diff --git a/.circleci/config.yml b/.circleci/config.yml index 3fa213982b9f..d688fc738005 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -441,7 +441,7 @@ jobs: # path: cover/excoveralls.html # - store_test_results: # path: _build/test/junit - # test_parity_http_websocket: + # test_nethermind_http_websocket: # docker: # # Ensure .tool-versions matches # - image: circleci/elixir:1.10.3-node-browsers @@ -451,8 +451,8 @@ jobs: # PGPASSWORD: postgres # # match POSTGRES_USER for postgres image below # PGUSER: postgres - # ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.HTTPWebSocket" - # ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Parity" + # ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.HTTPWebSocket" + # ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Nethermind" # - image: circleci/postgres:10.10-alpine # environment: # # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database @@ -480,13 +480,13 @@ jobs: # command: dockerize -wait tcp://localhost:5432 -timeout 1m # - run: - # name: mix test --exclude no_parity + # name: mix test --exclude no_nethermind # command: | # # Don't submit coverage report for forks, but let the build succeed # if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then - # mix coveralls.html --exclude no_parity --parallel --umbrella + # mix coveralls.html --exclude no_nethermind --parallel --umbrella # else - # mix coveralls.circle --exclude no_parity --parallel --umbrella || + # mix coveralls.circle --exclude no_nethermind --parallel --umbrella || # # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status # (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval) # fi @@ -495,7 +495,7 @@ jobs: # path: cover/excoveralls.html # - store_test_results: # path: _build/test/junit - test_parity_mox: + test_nethermind_mox: docker: # Ensure .tool-versions matches - image: circleci/elixir:1.10.3-node-browsers @@ -505,7 +505,7 @@ jobs: PGPASSWORD: postgres # match POSTGRES_USER for postgres image below PGUSER: postgres - ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox" + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" - image: circleci/postgres:10.10-alpine environment: @@ -534,13 +534,13 @@ jobs: command: dockerize -wait tcp://localhost:5432 -timeout 1m - run: - name: mix test --exclude no_parity + name: mix test --exclude no_nethermind command: | # Don't submit coverage report for forks, but let the build succeed if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then - mix coveralls.html --exclude no_parity --parallel --umbrella + mix coveralls.html --exclude no_nethermind --parallel --umbrella else - mix coveralls.circle --exclude no_parity --parallel --umbrella || + mix coveralls.circle --exclude no_nethermind --parallel --umbrella || # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval) fi @@ -571,8 +571,8 @@ workflows: # This unfortunately will only fire if all the tests pass because of how `requires` works - coveralls_merge: requires: - # - test_parity_http_websocket - - test_parity_mox + # - test_nethermind_http_websocket + - test_nethermind_mox # - test_geth_http_websocket # - test_geth_mox - credo: @@ -591,8 +591,8 @@ workflows: - eslint - jest - sobelow - # - test_parity_http_websocket - - test_parity_mox + # - test_nethermind_http_websocket + - test_nethermind_mox # - test_geth_http_websocket # - test_geth_mox - dialyzer: @@ -613,10 +613,10 @@ workflows: - sobelow: requires: - build - # - test_parity_http_websocket: + # - test_nethermind_http_websocket: # requires: # - build - - test_parity_mox: + - test_nethermind_mox: requires: - build # - test_geth_http_websocket: diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 7f10f09ac242..73a7edec27db 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -22,11 +22,11 @@ lib/explorer/smart_contract/reader.ex:435 lib/indexer/fetcher/token_total_supply_on_demand.ex:16 lib/explorer/exchange_rates/source.ex:116 lib/explorer/exchange_rates/source.ex:119 -lib/explorer/smart_contract/solidity/verifier.ex:223 +lib/explorer/smart_contract/solidity/verifier.ex:317 lib/block_scout_web/templates/address_contract/index.html.eex:158 lib/block_scout_web/templates/address_contract/index.html.eex:195 -lib/explorer/third_party_integrations/sourcify.ex:73 -lib/explorer/third_party_integrations/sourcify.ex:76 +lib/explorer/third_party_integrations/sourcify.ex:120 +lib/explorer/third_party_integrations/sourcify.ex:123 lib/block_scout_web/views/transaction_view.ex:137 lib/block_scout_web/views/transaction_view.ex:152 lib/block_scout_web/views/transaction_view.ex:197 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..b6a9ec46bde4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: "mix" + directory: "/" + open-pull-requests-limit: 20 + schedule: + interval: "daily" + + - package-ecosystem: "npm" + directory: "/apps/block_scout_web/assets" + open-pull-requests-limit: 10 + schedule: + interval: "daily" + + - package-ecosystem: "npm" + directory: "/apps/explorer" + open-pull-requests-limit: 10 + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000000..9b8a5cf42ab2 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '45 11 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index ee2559a1cfae..dbd489dcecef 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -12,11 +12,14 @@ env: MIX_ENV: test OTP_VERSION: '24.3.4.1' ELIXIR_VERSION: '1.13.4' + ACCOUNT_AUTH0_DOMAIN: 'blockscoutcom.us.auth0.com' + ACCOUNT_AUTH0_LOGOUT_URL: 'https://blockscoutcom.us.auth0.com/v2/logout' + ACCOUNT_AUTH0_LOGOUT_RETURN_URL: 'https://blockscout.com/auth/logout' jobs: build-and-cache: name: Build and Cache deps - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: erlef/setup-beam@v1 @@ -34,7 +37,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -81,7 +84,7 @@ jobs: credo: name: Credo - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache steps: - uses: actions/checkout@v2 @@ -89,12 +92,12 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -105,7 +108,7 @@ jobs: check_formatted: name: Code formatting checks - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache steps: - uses: actions/checkout@v2 @@ -118,7 +121,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -128,7 +131,7 @@ jobs: - run: mix format --check-formatted dialyzer: name: Dialyzer static analysis - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache steps: - uses: actions/checkout@v2 @@ -141,7 +144,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -153,7 +156,7 @@ jobs: id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_12-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-" @@ -165,10 +168,35 @@ jobs: - name: Run Dialyzer run: mix dialyzer --halt-exit-status + gettext: + name: Missing translation keys check + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + 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: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ 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 + working-directory: "apps/block_scout_web" sobelow: name: Sobelow security analysis - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache steps: - uses: actions/checkout@v2 @@ -181,7 +209,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -196,7 +224,7 @@ jobs: working-directory: "apps/block_scout_web" eslint: name: ESLint - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache steps: - uses: actions/checkout@v2 @@ -209,7 +237,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -242,7 +270,7 @@ jobs: working-directory: apps/block_scout_web/assets jest: name: JS Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache steps: - uses: actions/checkout@v2 @@ -255,7 +283,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -278,9 +306,9 @@ jobs: - run: ./node_modules/.bin/jest working-directory: apps/block_scout_web/assets - test_parity_mox_ethereum_jsonrpc: + test_nethermind_mox_ethereum_jsonrpc: name: EthereumJSONRPC Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache services: postgres: @@ -314,7 +342,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -322,21 +350,21 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - run: ./bin/install_chrome_headless.sh - - name: mix test --exclude no_parity + - name: mix test --exclude no_nethermind run: | cd apps/ethereum_jsonrpc mix compile - mix test --no-start --exclude no_parity + mix test --no-start --exclude no_nethermind env: # match POSTGRES_PASSWORD for postgres image below PGPASSWORD: postgres # match POSTGRES_USER for postgres image below PGUSER: postgres - ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox" + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" - test_parity_mox_explorer: + test_nethermind_mox_explorer: name: Explorer Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache services: postgres: @@ -370,7 +398,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -387,23 +415,23 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm - run: ./bin/install_chrome_headless.sh - - name: mix test --exclude no_parity + - name: mix test --exclude no_nethermind run: | mix ecto.create --quiet mix ecto.migrate cd apps/explorer mix compile - mix test --no-start --exclude no_parity + mix test --no-start --exclude no_nethermind env: # match POSTGRES_PASSWORD for postgres image below PGPASSWORD: postgres # match POSTGRES_USER for postgres image below PGUSER: postgres - ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox" + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" - test_parity_mox_indexer: + test_nethermind_mox_indexer: name: Indexer Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache services: postgres: @@ -437,7 +465,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -447,26 +475,31 @@ jobs: - run: ./bin/install_chrome_headless.sh - - name: mix test --exclude no_parity + - name: mix test --exclude no_nethermind run: | mix ecto.create --quiet mix ecto.migrate cd apps/indexer mix compile - mix test --no-start --exclude no_parity + mix test --no-start --exclude no_nethermind env: # match POSTGRES_PASSWORD for postgres image below PGPASSWORD: postgres # match POSTGRES_USER for postgres image below PGUSER: postgres - ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox" + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" - test_parity_mox_block_scout_web: + test_nethermind_mox_block_scout_web: name: Blockscout Web Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: build-and-cache services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + postgres: image: postgres env: @@ -498,7 +531,7 @@ 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_12-${{ hashFiles('mix.lock') }} @@ -530,19 +563,22 @@ jobs: - run: ./bin/install_chrome_headless.sh - - name: mix test --exclude no_parity + - name: mix test --exclude no_nethermind run: | mix ecto.create --quiet mix ecto.migrate cd apps/block_scout_web mix compile - mix test --no-start --exclude no_parity + mix test --no-start --exclude no_nethermind env: # match POSTGRES_PASSWORD for postgres image below PGPASSWORD: postgres # match POSTGRES_USER for postgres image below PGUSER: postgres - ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox" + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" CHAIN_ID: "77" - ADMIN_PANEL_ENABLED: "true" \ No newline at end of file + ADMIN_PANEL_ENABLED: "true" + ACCOUNT_ENABLED: "true" + ACCOUNT_REDIS_URL: "redis://localhost:6379" + API_V2_ENABLED: "true" diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml new file mode 100644 index 000000000000..ec503dd9d1c9 --- /dev/null +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -0,0 +1,82 @@ +name: Publish Docker image on every push to master branch + +on: + push: + branches: + - master +env: + OTP_VERSION: '24.3.4.1' + ELIXIR_VERSION: '1.13.4' + NEXT_RELEASE_VERSION: 5.0.0 + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + outputs: + release-version: ${{ steps.output-step.outputs.release-version }} + short-sha: ${{ steps.output-step.outputs.short-sha }} + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: blockscout/blockscout + + - name: Add SHORT_SHA env property with commit short sha + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + + - name: Add outputs + run: | + echo "::set-output name=release-version::${{ env.NEXT_RELEASE_VERSION }}" + echo "::set-output name=short-sha::${{ env.SHORT_SHA }}" + id: output-step + + - name: Build and push Docker image + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout:buildcache + cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max + tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.NEXT_RELEASE_VERSION }}-prerelease-${{ env.SHORT_SHA }} + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_READ_API=false + API_PATH= + NETWORK_PATH= + DISABLE_WEBAPP=false + DISABLE_WRITE_API=false + CACHE_ENABLE_TOTAL_GAS_USAGE_COUNTER= + WOBSERVER_ENABLED=false + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + DISABLE_BRIDGE_MARKET_CAP_UPDATER=false + CACHE_BRIDGE_MARKET_CAP_UPDATE_INTERVAL= + SOCKET_ROOT= + tests: + needs: push_to_registry + uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master + with: + blockscoutImage: blockscout/blockscout:${{ needs.push_to_registry.outputs.release-version }}-prerelease-${{ needs.push_to_registry.outputs.short-sha }} + blockscoutIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT + frontendIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT + gethIngressHost: e2e-geth-$GITHUB_SHA_SHORT + scVerifierIngressHost: e2e-sc-verifier-$GITHUB_SHA_SHORT + secrets: inherit diff --git a/.github/workflows/publish-docker-image-release.yml b/.github/workflows/publish-docker-image-release.yml new file mode 100644 index 000000000000..05861878b950 --- /dev/null +++ b/.github/workflows/publish-docker-image-release.yml @@ -0,0 +1,111 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Publish Docker image + +on: + release: + types: [published] + +env: + OTP_VERSION: '24.3.4.1' + ELIXIR_VERSION: '1.13.4' + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 4.1.8 + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: blockscout/blockscout + + - name: Build & Push Docker image + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout:buildcache + cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max + tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }} + platforms: | + linux/arm64 + linux/amd64 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_READ_API=false + API_PATH=/ + NETWORK_PATH=/ + DISABLE_WEBAPP=false + DISABLE_WRITE_API=false + CACHE_ENABLE_TOTAL_GAS_USAGE_COUNTER= + WOBSERVER_ENABLED=false + ADMIN_PANEL_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + SOCKET_ROOT= + + merge-master-after-release: + name: Merge 'master' to specific branch after release + runs-on: ubuntu-latest + env: + BRANCHES: | + production-core-stg + production-eth-stg + production-harmony-mainnet-shard-0-stg + production-lukso-stg + production-optimism-goerli-stg + production-optimism-bedrock-goerli-stg + production-optimism-mainnet-stg + production-optimism-stg + production-rsk-stg + production-sokol-stg + production-xdai-stg + steps: + - uses: actions/checkout@v2 + - name: Set Git config + run: | + git config --local user.email "actions@github.com" + git config --local user.name "Github Actions" + - name: Merge master back after release + run: | + git fetch --unshallow + touch errors.txt + for branch in $BRANCHES; + do + git reset --merge + git checkout master + git fetch origin + echo $branch + git ls-remote --exit-code --heads origin $branch || { echo $branch >> errors.txt; continue; } + echo "Merge 'master' to $branch" + git checkout $branch + git pull || { echo $branch >> errors.txt; continue; } + git merge --no-ff master -m "Auto-merge master back to $branch" || { echo $branch >> errors.txt; continue; } + git push || { echo $branch >> errors.txt; continue; } + git checkout master; + done + [ -s errors.txt ] && echo "There are problems with merging 'master' to branches:" || echo "Errors file is empty" + cat errors.txt + [ ! -s errors.txt ] diff --git a/.gitignore b/.gitignore index bdb24a89a4b8..0bc441a151f4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ /*.ez /logs +# mix dialyzer artifacts +/priv/plts + # Generated on crash by the VM erl_crash.dump @@ -44,7 +47,11 @@ screenshots/ /apps/block_scout_web/priv/cert /docker-compose/postgres-data +/docker-compose/redis-data /docker-compose/tmp +/docker-compose/logs .idea/ *.iml + +.vscode diff --git a/.tool-versions b/.tool-versions index dbd2f347d9ee..edcbcfec6cef 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.13.4-otp-24 -erlang 24.3.4.1 -nodejs 16.15.1 +elixir 1.14.2-otp-25 +erlang 25.1.1 +nodejs 16.16.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7436c8418687..029847ccb7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,46 +1,440 @@ ## Current ### Features + +- [#6433](https://github.com/blockscout/blockscout/pull/6433) - Update error pagess +- [#6544](https://github.com/blockscout/blockscout/pull/6544) - API improvements +- [#5561](https://github.com/blockscout/blockscout/pull/5561), [#6523](https://github.com/blockscout/blockscout/pull/6523), [#6549](https://github.com/blockscout/blockscout/pull/6549) - Improve working with contracts implementations +- [#6401](https://github.com/blockscout/blockscout/pull/6401) - Add Sol2Uml contract visualization +- [#6481](https://github.com/blockscout/blockscout/pull/6481) - Smart contract verification improvements +- [#6444](https://github.com/blockscout/blockscout/pull/6444) - Add support for yul verification via rust microservice +- [#6440](https://github.com/blockscout/blockscout/pull/6440) - Add support for base64 encoded NFT metadata +- [#6407](https://github.com/blockscout/blockscout/pull/6407) - Indexed ratio for int txs fetching stage +- [#6324](https://github.com/blockscout/blockscout/pull/6324) - Add verified contracts list page +- [#6379](https://github.com/blockscout/blockscout/pull/6379), [#6429](https://github.com/blockscout/blockscout/pull/6429) - API v2 for frontend +- [#6351](https://github.com/blockscout/blockscout/pull/6351) - Enable forum link env var +- [#6316](https://github.com/blockscout/blockscout/pull/6316) - Copy public tags functionality to master +- [#6196](https://github.com/blockscout/blockscout/pull/6196) - INDEXER_CATCHUP_BLOCKS_BATCH_SIZE and INDEXER_CATCHUP_BLOCKS_CONCURRENCY env varaibles +- [#6187](https://github.com/blockscout/blockscout/pull/6187) - Filter by created time of verified contracts in listcontracts API endpoint +- [#6092](https://github.com/blockscout/blockscout/pull/6092) - Blockscout Account functionality +- [#6073](https://github.com/blockscout/blockscout/pull/6073) - Add vyper support for rust verifier microservice integration +- [#6111](https://github.com/blockscout/blockscout/pull/6111) - Add Prometheus metrics to indexer +- [#6168](https://github.com/blockscout/blockscout/pull/6168) - Token instance fetcher checks instance owner and updates current token balance +- [#6209](https://github.com/blockscout/blockscout/pull/6209) - Add metrics for block import stages, runners, steps +- [#6257](https://github.com/blockscout/blockscout/pull/6257), [#6276](https://github.com/blockscout/blockscout/pull/6276) - DISABLE_TOKEN_INSTANCE_FETCHER env variable +- [#6391](https://github.com/blockscout/blockscout/pull/6391), [#6427](https://github.com/blockscout/blockscout/pull/6427) - TokenTransfer token_id -> token_ids migration +- [#6443](https://github.com/blockscout/blockscout/pull/6443) - Drop internal transactions order index +- [#6450](https://github.com/blockscout/blockscout/pull/6450) - INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE and INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY env variables +- [#6454](https://github.com/blockscout/blockscout/pull/6454) - INDEXER_RECEIPTS_BATCH_SIZE, INDEXER_RECEIPTS_CONCURRENCY, INDEXER_COIN_BALANCES_BATCH_SIZE, INDEXER_COIN_BALANCES_CONCURRENCY env variables +- [#6476](https://github.com/blockscout/blockscout/pull/6476), [#6484](https://github.com/blockscout/blockscout/pull/6484) - Update token balances indexes +- [#6510](https://github.com/blockscout/blockscout/pull/6510) - Set consensus: false for blocks on int transaction foreign_key_violation +- [#6565](https://github.com/blockscout/blockscout/pull/6565) - Set restart: :permanent for permanent fetchers +- [#6568](https://github.com/blockscout/blockscout/pull/6568) - Drop unfetched_token_balances index + +### Fixes + +- [#6512](https://github.com/blockscout/blockscout/pull/6512) - Allow gasUsed in failed internal txs; Leave error field for staticcall +- [#6532](https://github.com/blockscout/blockscout/pull/6532) - Fix index creation migration +- [#6473](https://github.com/blockscout/blockscout/pull/6473) - Fix state changes for contract creation transactions +- [#6475](https://github.com/blockscout/blockscout/pull/6475) - Fix token name with unicode graphemes shortening +- [#6420](https://github.com/blockscout/blockscout/pull/6420) - Fix address logs search +- [#6390](https://github.com/blockscout/blockscout/pull/6390), [#6502](https://github.com/blockscout/blockscout/pull/6502), [#6511](https://github.com/blockscout/blockscout/pull/6511) - Fix transactions responses in API v2 +- [#6357](https://github.com/blockscout/blockscout/pull/6357), [#6409](https://github.com/blockscout/blockscout/pull/6409), [#6428](https://github.com/blockscout/blockscout/pull/6428) - Fix definitions of NETWORK_PATH, API_PATH, SOCKET_ROOT: process trailing slash +- [#6338](https://github.com/blockscout/blockscout/pull/6338) - Fix token search with space +- [#6329](https://github.com/blockscout/blockscout/pull/6329) - Prevent logger from truncating response from rust verifier service in case of an error +- [#6309](https://github.com/blockscout/blockscout/pull/6309) - Fix read contract bug and change address tx count +- [#6303](https://github.com/blockscout/blockscout/pull/6303) - Fix some UI bugs +- [#6243](https://github.com/blockscout/blockscout/pull/6243) - Fix freezes on `/blocks` page +- [#6162](https://github.com/blockscout/blockscout/pull/6162) - Extend token symbol type varchar(255) -> text +- [#6158](https://github.com/blockscout/blockscout/pull/6158) - Add missing clause for merge_twin_vyper_contract_with_changeset function +- [#6090](https://github.com/blockscout/blockscout/pull/6090) - Fix metadata fetching for ERC-1155 tokens instances +- [#6091](https://github.com/blockscout/blockscout/pull/6091) - Improve fetching media type for NFT +- [#6094](https://github.com/blockscout/blockscout/pull/6094) - Fix inconsistent behaviour of `getsourcecode` method +- [#6105](https://github.com/blockscout/blockscout/pull/6105) - Fix some token transfers broadcasting +- [#6106](https://github.com/blockscout/blockscout/pull/6106) - Fix 500 response on `/coin-balance` for empty address +- [#6118](https://github.com/blockscout/blockscout/pull/6118) - Fix unfetched token balances +- [#6163](https://github.com/blockscout/blockscout/pull/6163) - Fix rate limit logs +- [#6223](https://github.com/blockscout/blockscout/pull/6223) - Fix coin_id test +- [#6336](https://github.com/blockscout/blockscout/pull/6336) - Fix sending request on each key in token search +- [#6327](https://github.com/blockscout/blockscout/pull/6327) - Fix and refactor address logs page and search +- [#6449](https://github.com/blockscout/blockscout/pull/6449) - Search min_missing_block_number from zero +- [#6492](https://github.com/blockscout/blockscout/pull/6492) - Remove token instance owner fetching +- [#6536](https://github.com/blockscout/blockscout/pull/6536) - Fix internal transactions query +- [#6550](https://github.com/blockscout/blockscout/pull/6550) - Query token transfers before updating + +### Chore + +- [#6584](https://github.com/blockscout/blockscout/pull/6584) - Vacuum package-lock.json +- [#6581](https://github.com/blockscout/blockscout/pull/6581) - Dark mode switcher localStorage to cookie in order to support new UI +- [#6572](https://github.com/blockscout/blockscout/pull/6572) - pending_block_operations table: remove fetch_internal_transactions column +- [#6387](https://github.com/blockscout/blockscout/pull/6387) - Fix errors in docker-build and e2e-tests workflows +- [#6325](https://github.com/blockscout/blockscout/pull/6325) - Set http_only attribute of account authorization cookie to false +- [#6343](https://github.com/blockscout/blockscout/pull/6343) - Docker-compose persistent logs +- [#6240](https://github.com/blockscout/blockscout/pull/6240) - Elixir 1.14 support +- [#6204](https://github.com/blockscout/blockscout/pull/6204) - Refactor contract libs render, CONTRACT_VERIFICATION_MAX_LIBRARIES, refactor parsing integer env vars in config +- [#6195](https://github.com/blockscout/blockscout/pull/6195) - Docker compose configs improvements: Redis container name and persistent storage +- [#6192](https://github.com/blockscout/blockscout/pull/6192), [#6207](https://github.com/blockscout/blockscout/pull/6207) - Hide Indexing Internal Transactions message, if INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=true +- [#6183](https://github.com/blockscout/blockscout/pull/6183) - Transparent coin name definition +- [#6155](https://github.com/blockscout/blockscout/pull/6155), [#6189](https://github.com/blockscout/blockscout/pull/6189) - Refactor Ethereum JSON RPC variants +- [#6125](https://github.com/blockscout/blockscout/pull/6125) - Rename obsolete "parity" EthereumJSONRPC.Variant to "nethermind" +- [#6124](https://github.com/blockscout/blockscout/pull/6124) - Docker compose: add config for Erigon +- [#6061](https://github.com/blockscout/blockscout/pull/6061) - Discord badge and updated permalink +
+ Dependencies version bumps + +- [#6053](https://github.com/blockscout/blockscout/pull/6053) - Bump jest-environment-jsdom from 29.0.1 to 29.0.2 in /apps/block_scout_web/assets +- [#6055](https://github.com/blockscout/blockscout/pull/6055) - Bump @babel/core from 7.18.13 to 7.19.0 in /apps/block_scout_web/assets +- [#6054](https://github.com/blockscout/blockscout/pull/6054) - Bump jest from 29.0.1 to 29.0.2 in /apps/block_scout_web/assets +- [#6056](https://github.com/blockscout/blockscout/pull/6056) - Bump @babel/preset-env from 7.18.10 to 7.19.0 in /apps/block_scout_web/assets +- [#6064](https://github.com/blockscout/blockscout/pull/6064) - Bump sweetalert2 from 11.4.29 to 11.4.31 in /apps/block_scout_web/assets +- [#6075](https://github.com/blockscout/blockscout/pull/6075) - Bump sweetalert2 from 11.4.31 to 11.4.32 in /apps/block_scout_web/assets +- [#6082](https://github.com/blockscout/blockscout/pull/6082) - Bump core-js from 3.25.0 to 3.25.1 in /apps/block_scout_web/assets +- [#6083](https://github.com/blockscout/blockscout/pull/6083) - Bump sass from 1.54.8 to 1.54.9 in /apps/block_scout_web/assets +- [#6095](https://github.com/blockscout/blockscout/pull/6095) - Bump jest-environment-jsdom from 29.0.2 to 29.0.3 in /apps/block_scout_web/assets +- [#6096](https://github.com/blockscout/blockscout/pull/6096) - Bump exvcr from 0.13.3 to 0.13.4 +- [#6101](https://github.com/blockscout/blockscout/pull/6101) - Bump ueberauth from 0.10.1 to 0.10.2 +- [#6102](https://github.com/blockscout/blockscout/pull/6102) - Bump eslint from 8.23.0 to 8.23.1 in /apps/block_scout_web/assets +- [#6098](https://github.com/blockscout/blockscout/pull/6098) - Bump ex_json_schema from 0.9.1 to 0.9.2 +- [#6097](https://github.com/blockscout/blockscout/pull/6097) - Bump autoprefixer from 10.4.8 to 10.4.9 in /apps/block_scout_web/assets +- [#6099](https://github.com/blockscout/blockscout/pull/6099) - Bump jest from 29.0.2 to 29.0.3 in /apps/block_scout_web/assets +- [#6103](https://github.com/blockscout/blockscout/pull/6103) - Bump css-minimizer-webpack-plugin from 4.0.0 to 4.1.0 in /apps/block_scout_web/assets +- [#6108](https://github.com/blockscout/blockscout/pull/6108) - Bump autoprefixer from 10.4.9 to 10.4.10 in /apps/block_scout_web/assets +- [#6116](https://github.com/blockscout/blockscout/pull/6116) - Bump autoprefixer from 10.4.10 to 10.4.11 in /apps/block_scout_web/assets +- [#6114](https://github.com/blockscout/blockscout/pull/6114) - Bump @babel/core from 7.19.0 to 7.19.1 in /apps/block_scout_web/assets +- [#6113](https://github.com/blockscout/blockscout/pull/6113) - Bump ueberauth from 0.10.2 to 0.10.3 +- [#6112](https://github.com/blockscout/blockscout/pull/6112) - Bump @babel/preset-env from 7.19.0 to 7.19.1 in /apps/block_scout_web/assets +- [#6115](https://github.com/blockscout/blockscout/pull/6115) - Bump web3 from 1.7.5 to 1.8.0 in /apps/block_scout_web/assets +- [#6117](https://github.com/blockscout/blockscout/pull/6117) - Bump sweetalert2 from 11.4.32 to 11.4.33 in /apps/block_scout_web/assets +- [#6119](https://github.com/blockscout/blockscout/pull/6119) - Bump scss-tokenizer from 0.3.0 to 0.4.3 in /apps/block_scout_web/assets +- [#6138](https://github.com/blockscout/blockscout/pull/6138) - Bump core-js from 3.25.1 to 3.25.2 in /apps/block_scout_web/assets +- [#6147](https://github.com/blockscout/blockscout/pull/6147) - Bump autoprefixer from 10.4.11 to 10.4.12 in /apps/block_scout_web/assets +- [#6151](https://github.com/blockscout/blockscout/pull/6151) - Bump sass from 1.54.9 to 1.55.0 in /apps/block_scout_web/assets +- [#6173](https://github.com/blockscout/blockscout/pull/6173) - Bump core-js from 3.25.2 to 3.25.3 in /apps/block_scout_web/assets +- [#6174](https://github.com/blockscout/blockscout/pull/6174) - Bump sweetalert2 from 11.4.33 to 11.4.34 in /apps/block_scout_web/assets +- [#6175](https://github.com/blockscout/blockscout/pull/6175) - Bump luxon from 3.0.3 to 3.0.4 in /apps/block_scout_web/assets +- [#6176](https://github.com/blockscout/blockscout/pull/6176) - Bump @babel/preset-env from 7.19.1 to 7.19.3 in /apps/block_scout_web/assets +- [#6177](https://github.com/blockscout/blockscout/pull/6177) - Bump @babel/core from 7.19.1 to 7.19.3 in /apps/block_scout_web/assets +- [#6178](https://github.com/blockscout/blockscout/pull/6178) - Bump eslint from 8.23.1 to 8.24.0 in /apps/block_scout_web/assets +- [#6184](https://github.com/blockscout/blockscout/pull/6184) - Bump jest from 29.0.3 to 29.1.1 in /apps/block_scout_web/assets +- [#6186](https://github.com/blockscout/blockscout/pull/6186) - Bump jest-environment-jsdom from 29.0.3 to 29.1.1 in /apps/block_scout_web/assets +- [#6185](https://github.com/blockscout/blockscout/pull/6185) - Bump sweetalert2 from 11.4.34 to 11.4.35 in /apps/block_scout_web/assets +- [#6146](https://github.com/blockscout/blockscout/pull/6146) - Bump websocket_client from 1.3.0 to 1.5.0 +- [#6191](https://github.com/blockscout/blockscout/pull/6191) - Bump css-minimizer-webpack-plugin from 4.1.0 to 4.2.0 in /apps/block_scout_web/assets +- [#6199](https://github.com/blockscout/blockscout/pull/6199) - Bump redix from 1.1.5 to 1.2.0 +- [#6213](https://github.com/blockscout/blockscout/pull/6213) - Bump sweetalert2 from 11.4.35 to 11.4.37 in /apps/block_scout_web/assets +- [#6214](https://github.com/blockscout/blockscout/pull/6214) - Bump jest-environment-jsdom from 29.1.1 to 29.1.2 in /apps/block_scout_web/assets +- [#6215](https://github.com/blockscout/blockscout/pull/6215) - Bump postcss from 8.4.16 to 8.4.17 in /apps/block_scout_web/assets +- [#6216](https://github.com/blockscout/blockscout/pull/6216) - Bump core-js from 3.25.3 to 3.25.5 in /apps/block_scout_web/assets +- [#6217](https://github.com/blockscout/blockscout/pull/6217) - Bump jest from 29.1.1 to 29.1.2 in /apps/block_scout_web/assets +- [#6229](https://github.com/blockscout/blockscout/pull/6229) - Bump sweetalert2 from 11.4.37 to 11.4.38 in /apps/block_scout_web/assets +- [#6232](https://github.com/blockscout/blockscout/pull/6232) - Bump css-minimizer-webpack-plugin from 4.2.0 to 4.2.1 in /apps/block_scout_web/assets +- [#6230](https://github.com/blockscout/blockscout/pull/6230) - Bump sass-loader from 13.0.2 to 13.1.0 in /apps/block_scout_web/assets +- [#6251](https://github.com/blockscout/blockscout/pull/6251) - Bump sweetalert2 from 11.4.38 to 11.5.1 in /apps/block_scout_web/assets +- [#6246](https://github.com/blockscout/blockscout/pull/6246) - Bump @babel/preset-env from 7.19.3 to 7.19.4 in /apps/block_scout_web/assets +- [#6247](https://github.com/blockscout/blockscout/pull/6247) - Bump ex_abi from 0.5.14 to 0.5.15 +- [#6248](https://github.com/blockscout/blockscout/pull/6248) - Bump eslint from 8.24.0 to 8.25.0 in /apps/block_scout_web/assets +- [#6255](https://github.com/blockscout/blockscout/pull/6255) - Bump postcss from 8.4.17 to 8.4.18 in /apps/block_scout_web/assets +- [#6256](https://github.com/blockscout/blockscout/pull/6256) - Bump css-minimizer-webpack-plugin from 4.2.1 to 4.2.2 in /apps/block_scout_web/assets +- [#6258](https://github.com/blockscout/blockscout/pull/6258) - Bump jest from 29.1.2 to 29.2.0 in /apps/block_scout_web/assets +- [#6259](https://github.com/blockscout/blockscout/pull/6259) - Bump jest-environment-jsdom from 29.1.2 to 29.2.0 in /apps/block_scout_web/assets +- [#6253](https://github.com/blockscout/blockscout/pull/6253) - Bump eslint-plugin-promise from 6.0.1 to 6.1.0 in /apps/block_scout_web/assets +- [#6279](https://github.com/blockscout/blockscout/pull/6279) - Bump util from 0.12.4 to 0.12.5 in /apps/block_scout_web/assets +- [#6280](https://github.com/blockscout/blockscout/pull/6280) - Bump ex_rlp from 0.5.4 to 0.5.5 +- [#6281](https://github.com/blockscout/blockscout/pull/6281) - Bump ex_abi from 0.5.15 to 0.5.16 +- [#6283](https://github.com/blockscout/blockscout/pull/6283) - Bump spandex_datadog from 1.2.0 to 1.3.0 +- [#6282](https://github.com/blockscout/blockscout/pull/6282) - Bump sweetalert2 from 11.5.1 to 11.5.2 in /apps/block_scout_web/assets +- [#6284](https://github.com/blockscout/blockscout/pull/6284) - Bump spandex_phoenix from 1.0.6 to 1.1.0 +- [#6298](https://github.com/blockscout/blockscout/pull/6298) - Bump jest-environment-jsdom from 29.2.0 to 29.2.1 in /apps/block_scout_web/assets +- [#6297](https://github.com/blockscout/blockscout/pull/6297) - Bump jest from 29.2.0 to 29.2.1 in /apps/block_scout_web/assets +- [#6254](https://github.com/blockscout/blockscout/pull/6254) - Bump ex_doc from 0.28.5 to 0.28.6 +- [#6314](https://github.com/blockscout/blockscout/pull/6314) - Bump @babel/core from 7.19.3 to 7.19.6 in /apps/block_scout_web/assets +- [#6313](https://github.com/blockscout/blockscout/pull/6313) - Bump ex_doc from 0.28.6 to 0.29.0 +- [#6305](https://github.com/blockscout/blockscout/pull/6305) - Bump sweetalert2 from 11.5.2 to 11.6.0 in /apps/block_scout_web/assets +- [#6312](https://github.com/blockscout/blockscout/pull/6312) - Bump eslint-plugin-promise from 6.1.0 to 6.1.1 in /apps/block_scout_web/assets +- [#6318](https://github.com/blockscout/blockscout/pull/6318) - Bump spandex from 3.1.0 to 3.2.0 +- [#6335](https://github.com/blockscout/blockscout/pull/6335) - Bump eslint from 8.25.0 to 8.26.0 in /apps/block_scout_web/assets +- [#6334](https://github.com/blockscout/blockscout/pull/6334) - Bump ex_cldr_numbers from 2.27.3 to 2.28.0 +- [#6333](https://github.com/blockscout/blockscout/pull/6333) - Bump core-js from 3.25.5 to 3.26.0 in /apps/block_scout_web/assets +- [#6332](https://github.com/blockscout/blockscout/pull/6332) - Bump ex_cldr from 2.33.2 to 2.34.0 +- [#6339](https://github.com/blockscout/blockscout/pull/6339) - Bump sweetalert2 from 11.6.0 to 11.6.2 in /apps/block_scout_web/assets +- [#6330](https://github.com/blockscout/blockscout/pull/6330) - Bump ex_cldr_units from 3.14.0 to 3.15.0 +- [#6341](https://github.com/blockscout/blockscout/pull/6341) - Bump jest-environment-jsdom from 29.2.1 to 29.2.2 in /apps/block_scout_web/assets +- [#6342](https://github.com/blockscout/blockscout/pull/6342) - Bump jest from 29.2.1 to 29.2.2 in /apps/block_scout_web/assets +- [#6359](https://github.com/blockscout/blockscout/pull/6359) - Bump babel-loader from 8.2.5 to 9.0.0 in /apps/block_scout_web/assets +- [#6360](https://github.com/blockscout/blockscout/pull/6360) - Bump sweetalert2 from 11.6.2 to 11.6.4 in /apps/block_scout_web/assets +- [#6363](https://github.com/blockscout/blockscout/pull/6363) - Bump autoprefixer from 10.4.12 to 10.4.13 in /apps/block_scout_web/assets +- [#6364](https://github.com/blockscout/blockscout/pull/6364) - Bump ueberauth_auth0 from 2.0.0 to 2.1.0 +- [#6372](https://github.com/blockscout/blockscout/pull/6372) - Bump babel-loader from 9.0.0 to 9.0.1 in /apps/block_scout_web/assets +- [#6374](https://github.com/blockscout/blockscout/pull/6374) - Bump plug_cowboy from 2.5.2 to 2.6.0 +- [#6373](https://github.com/blockscout/blockscout/pull/6373) - Bump luxon from 3.0.4 to 3.1.0 in /apps/block_scout_web/assets +- [#6375](https://github.com/blockscout/blockscout/pull/6375) - Bump sweetalert2 from 11.6.4 to 11.6.5 in /apps/block_scout_web/assets +- [#6393](https://github.com/blockscout/blockscout/pull/6393) - Bump babel-loader from 9.0.1 to 9.1.0 in /apps/block_scout_web/assets +- [#6417](https://github.com/blockscout/blockscout/pull/6417) - Bump loader-utils from 2.0.2 to 2.0.3 in /apps/block_scout_web/assets +- [#6410](https://github.com/blockscout/blockscout/pull/6410) - Bump sweetalert2 from 11.6.5 to 11.6.7 in /apps/block_scout_web/assets +- [#6411](https://github.com/blockscout/blockscout/pull/6411) - Bump eslint from 8.26.0 to 8.27.0 in /apps/block_scout_web/assets +- [#6412](https://github.com/blockscout/blockscout/pull/6412) - Bump sass from 1.55.0 to 1.56.0 in /apps/block_scout_web/assets +- [#6413](https://github.com/blockscout/blockscout/pull/6413) - Bump jest-environment-jsdom from 29.2.2 to 29.3.0 in /apps/block_scout_web/assets +- [#6414](https://github.com/blockscout/blockscout/pull/6414) - Bump @babel/core from 7.19.6 to 7.20.2 in /apps/block_scout_web/assets +- [#6416](https://github.com/blockscout/blockscout/pull/6416) - Bump @babel/preset-env from 7.19.4 to 7.20.2 in /apps/block_scout_web/assets +- [#6419](https://github.com/blockscout/blockscout/pull/6419) - Bump jest from 29.2.2 to 29.3.1 in /apps/block_scout_web/assets +- [#6421](https://github.com/blockscout/blockscout/pull/6421) - Bump webpack from 5.74.0 to 5.75.0 in /apps/block_scout_web/assets +- [#6423](https://github.com/blockscout/blockscout/pull/6423) - Bump jest-environment-jsdom from 29.3.0 to 29.3.1 in /apps/block_scout_web/assets +- [#6424](https://github.com/blockscout/blockscout/pull/6424) - Bump floki from 0.33.1 to 0.34.0 +- [#6422](https://github.com/blockscout/blockscout/pull/6422) - Bump sass from 1.56.0 to 1.56.1 in /apps/block_scout_web/assets +- [#6430](https://github.com/blockscout/blockscout/pull/6430) - Bump web3 from 1.8.0 to 1.8.1 in /apps/block_scout_web/assets +- [#6431](https://github.com/blockscout/blockscout/pull/6431) - Bump sweetalert2 from 11.6.7 to 11.6.8 in /apps/block_scout_web/assets +- [#6432](https://github.com/blockscout/blockscout/pull/6432) - Bump sass-loader from 13.1.0 to 13.2.0 in /apps/block_scout_web/assets +- [#6445](https://github.com/blockscout/blockscout/pull/6445) - Bump postcss from 8.4.18 to 8.4.19 in /apps/block_scout_web/assets +- [#6446](https://github.com/blockscout/blockscout/pull/6446) - Bump core-js from 3.26.0 to 3.26.1 in /apps/block_scout_web/assets +- [#6452](https://github.com/blockscout/blockscout/pull/6452) - Bump @fortawesome/fontawesome-free from 6.2.0 to 6.2.1 in /apps/block_scout_web/assets +- [#6456](https://github.com/blockscout/blockscout/pull/6456) - Bump loader-utils from 2.0.3 to 2.0.4 in /apps/block_scout_web/assets +- [#6462](https://github.com/blockscout/blockscout/pull/6462) - Bump chartjs-adapter-luxon from 1.2.0 to 1.2.1 in /apps/block_scout_web/assets +- [#6469](https://github.com/blockscout/blockscout/pull/6469) - Bump sweetalert2 from 11.6.8 to 11.6.9 in /apps/block_scout_web/assets +- [#6471](https://github.com/blockscout/blockscout/pull/6471) - Bump mini-css-extract-plugin from 2.6.1 to 2.7.0 in /apps/block_scout_web/assets +- [#6470](https://github.com/blockscout/blockscout/pull/6470) - Bump chart.js from 3.9.1 to 4.0.1 in /apps/block_scout_web/assets +- [#6472](https://github.com/blockscout/blockscout/pull/6472) - Bump webpack-cli from 4.10.0 to 5.0.0 in /apps/block_scout_web/assets +- [#6487](https://github.com/blockscout/blockscout/pull/6487) - Bump eslint from 8.27.0 to 8.28.0 in /apps/block_scout_web/assets +- [#6488](https://github.com/blockscout/blockscout/pull/6488) - Bump ex_doc from 0.29.0 to 0.29.1 +- [#6491](https://github.com/blockscout/blockscout/pull/6491) - Bump minimatch from 3.0.4 to 3.0.8 in /apps/block_scout_web/assets +- [#6479](https://github.com/blockscout/blockscout/pull/6479) - Bump ecto_sql from 3.9.0 to 3.9.1 +- [#6486](https://github.com/blockscout/blockscout/pull/6486) - Bump sweetalert2 from 11.6.9 to 11.6.10 in /apps/block_scout_web/assets +- [#6498](https://github.com/blockscout/blockscout/pull/6498) - Bump sweetalert2 from 11.6.10 to 11.6.13 in /apps/block_scout_web/assets +- [#6506](https://github.com/blockscout/blockscout/pull/6506) - Bump web3modal from 1.9.9 to 1.9.10 in /apps/block_scout_web/assets +- [#6505](https://github.com/blockscout/blockscout/pull/6505) - Bump highlight.js from 11.6.0 to 11.7.0 in /apps/block_scout_web/assets +- [#6504](https://github.com/blockscout/blockscout/pull/6504) - Bump sweetalert2 from 11.6.13 to 11.6.14 in /apps/block_scout_web/assets +- [#6507](https://github.com/blockscout/blockscout/pull/6507) - Bump remote_ip from 1.0.0 to 1.1.0 +- [#6497](https://github.com/blockscout/blockscout/pull/6497) - Bump chartjs-adapter-luxon from 1.2.1 to 1.3.0 in /apps/block_scout_web/assets +- [#6519](https://github.com/blockscout/blockscout/pull/6519) - Bump photoswipe from 5.3.3 to 5.3.4 in /apps/block_scout_web/assets +- [#6520](https://github.com/blockscout/blockscout/pull/6520) - Bump @babel/core from 7.20.2 to 7.20.5 in /apps/block_scout_web/assets +- [#6527](https://github.com/blockscout/blockscout/pull/6527) - Bump luxon from 3.1.0 to 3.1.1 in /apps/block_scout_web/assets +- [#6526](https://github.com/blockscout/blockscout/pull/6526) - Bump mini-css-extract-plugin from 2.7.0 to 2.7.1 in /apps/block_scout_web/assets +- [#6533](https://github.com/blockscout/blockscout/pull/6533) - Bump postcss-loader from 7.0.1 to 7.0.2 in /apps/block_scout_web/assets +- [#6534](https://github.com/blockscout/blockscout/pull/6534) - Bump sweetalert2 from 11.6.14 to 11.6.15 in /apps/block_scout_web/assets +- [#6539](https://github.com/blockscout/blockscout/pull/6539) - Bump decode-uri-component from 0.2.0 to 0.2.2 in /apps/block_scout_web/assets +- [#6555](https://github.com/blockscout/blockscout/pull/6555) - Bump bignumber.js from 9.1.0 to 9.1.1 in /apps/block_scout_web/assets +- [#6557](https://github.com/blockscout/blockscout/pull/6557) - Bump webpack-cli from 5.0.0 to 5.0.1 in /apps/block_scout_web/assets +- [#6558](https://github.com/blockscout/blockscout/pull/6558) - Bump eslint from 8.28.0 to 8.29.0 in /apps/block_scout_web/assets +- [#6556](https://github.com/blockscout/blockscout/pull/6556) - Bump mini-css-extract-plugin from 2.7.1 to 2.7.2 in /apps/block_scout_web/assets +- [#6562](https://github.com/blockscout/blockscout/pull/6562) - Bump qs from 6.5.2 to 6.5.3 in /apps/block_scout_web/assets +- [#6577](https://github.com/blockscout/blockscout/pull/6577) - Bump postcss from 8.4.19 to 8.4.20 in /apps/block_scout_web/assets +- [#6578](https://github.com/blockscout/blockscout/pull/6578) - Bump sass from 1.56.1 to 1.56.2 in /apps/block_scout_web/assets +
+ +## 4.1.8-beta + +### Features + +- [#5968](https://github.com/blockscout/blockscout/pull/5968) - Add call type in the response of txlistinternal API method +- [#5860](https://github.com/blockscout/blockscout/pull/5860) - Integrate rust verifier micro-service ([blockscout-rs/verifier](https://github.com/blockscout/blockscout-rs/tree/main/verification)) +- [#6001](https://github.com/blockscout/blockscout/pull/6001) - Add ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES env var that filters requests and query node only if the block quantity is "latest" +- [#5944](https://github.com/blockscout/blockscout/pull/5944) - Add tab with state changes to transaction page + +### Fixes + +- [#6038](https://github.com/blockscout/blockscout/pull/6038) - Extend token name from string to text type +- [#6037](https://github.com/blockscout/blockscout/pull/6037) - Fix order of results in txlistinternal API endpoint +- [#6036](https://github.com/blockscout/blockscout/pull/6036) - Fix address checksum on transaction page +- [#6032](https://github.com/blockscout/blockscout/pull/6032) - Sort by address.hash column in accountlist API endpoint +- [#6017](https://github.com/blockscout/blockscout/pull/6017), [#6028](https://github.com/blockscout/blockscout/pull/6028) - Move "contract interaction" and "Add chain to MM" env vars to runtime +- [#6012](https://github.com/blockscout/blockscout/pull/6012) - Fix display of estimated addresses counter on the main page +- [#5978](https://github.com/blockscout/blockscout/pull/5978) - Allow timestamp param in the log of eth_getTransactionReceipt method +- [#5977](https://github.com/blockscout/blockscout/pull/5977) - Fix address overview.html.eex in case of nil implementation address hash +- [#5975](https://github.com/blockscout/blockscout/pull/5975) - Fix CSV export of internal transactions +- [#5957](https://github.com/blockscout/blockscout/pull/5957) - Server-side reCAPTCHA check for CSV export +- [#5954](https://github.com/blockscout/blockscout/pull/5954) - Fix ace editor appearance +- [#5942](https://github.com/blockscout/blockscout/pull/5942), [#5945](https://github.com/blockscout/blockscout/pull/5945) - Fix nightly solidity versions filtering UX +- [#5904](https://github.com/blockscout/blockscout/pull/5904) - Enhance health API endpoint: better parsing HEALTHY_BLOCKS_PERIOD and use it in the response +- [#5903](https://github.com/blockscout/blockscout/pull/5903) - Disable compile env validation +- [#5887](https://github.com/blockscout/blockscout/pull/5887) - Added missing environment variables to Makefile container params +- [#5850](https://github.com/blockscout/blockscout/pull/5850) - Fix too large postgres notifications +- [#5809](https://github.com/blockscout/blockscout/pull/5809) - Fix 404 on `/metadata` page +- [#5807](https://github.com/blockscout/blockscout/pull/5807) - Update Makefile migrate command due to release build +- [#5786](https://github.com/blockscout/blockscout/pull/5786) - Replace `current_path` with `Controller.current_full_path` in two controllers +- [#5948](https://github.com/blockscout/blockscout/pull/5948) - Fix unexpected messages in `CoinBalanceOnDemand` +- [#6013](https://github.com/blockscout/blockscout/pull/6013) - Fix ERC-1155 tokens fetching +- [#6043](https://github.com/blockscout/blockscout/pull/6043) - Fix token instance fetching +- [#6093](https://github.com/blockscout/blockscout/pull/6093) - Fix Indexer.Fetcher.TokenInstance for ERC-1155 tokens + +### Chore + +- [#5921](https://github.com/blockscout/blockscout/pull/5921) - Bump briefly from 25942fb to 1dd66ee +- [#6033](https://github.com/blockscout/blockscout/pull/6033) - Bump sass from 1.54.7 to 1.54.8 in /apps/block_scout_web/assets +- [#6046](https://github.com/blockscout/blockscout/pull/6046) - Bump credo from 1.6.6 to 1.6.7 +- [#6045](https://github.com/blockscout/blockscout/pull/6045) - Re-use _btn_copy.html for raw trace page +- [#6035](https://github.com/blockscout/blockscout/pull/6035) - Hide copy btn if no raw trace +- [#6034](https://github.com/blockscout/blockscout/pull/6034) - Suppress empty sections in supported chain dropdown +- [#5939](https://github.com/blockscout/blockscout/pull/5939) - Bump sweetalert2 from 11.4.26 to 11.4.27 in /apps/block_scout_web/assets +- [#5938](https://github.com/blockscout/blockscout/pull/5938) - Bump xss from 1.0.13 to 1.0.14 in /apps/block_scout_web/assets +- [#5743](https://github.com/blockscout/blockscout/pull/5743) - Fixing tracer not found #5729 +- [#5952](https://github.com/blockscout/blockscout/pull/5952) - Bump sweetalert2 from 11.4.27 to 11.4.28 in /apps/block_scout_web/assets +- [#5955](https://github.com/blockscout/blockscout/pull/5955) - Bump ex_doc from 0.28.4 to 0.28.5 +- [#5956](https://github.com/blockscout/blockscout/pull/5956) - Bump bcrypt_elixir from 2.3.1 to 3.0.1 +- [#5964](https://github.com/blockscout/blockscout/pull/5964) - Bump sweetalert2 from 11.4.28 to 11.4.29 in /apps/block_scout_web/assets +- [#5966](https://github.com/blockscout/blockscout/pull/5966) - Bump sass from 1.54.4 to 1.54.5 in /apps/block_scout_web/assets +- [#5967](https://github.com/blockscout/blockscout/pull/5967) - Bump @babel/core from 7.18.10 to 7.18.13 in /apps/block_scout_web/assets +- [#5973](https://github.com/blockscout/blockscout/pull/5973) - Bump prometheus from 4.9.0 to 4.9.1 +- [#5974](https://github.com/blockscout/blockscout/pull/5974) - Bump cldr_utils from 2.19.0 to 2.19.1 +- [#5884](https://github.com/blockscout/blockscout/pull/5884) - Bump nimble_csv from 1.1.0 to 1.2.0 +- [#5984](https://github.com/blockscout/blockscout/pull/5984) - Bump jest from 28.1.3 to 29.0.0 in /apps/block_scout_web/assets +- [#5983](https://github.com/blockscout/blockscout/pull/5983) - Bump core-js from 3.24.1 to 3.25.0 in /apps/block_scout_web/assets +- [#5981](https://github.com/blockscout/blockscout/pull/5981) - Bump eslint-plugin-promise from 6.0.0 to 6.0.1 in /apps/block_scout_web/assets +- [#5982](https://github.com/blockscout/blockscout/pull/5982) - Bump jest-environment-jsdom from 28.1.3 to 29.0.0 in /apps/block_scout_web/assets +- [#5987](https://github.com/blockscout/blockscout/pull/5987) - Bump jest from 29.0.0 to 29.0.1 in /apps/block_scout_web/assets +- [#5988](https://github.com/blockscout/blockscout/pull/5988) - Bump jest-environment-jsdom from 29.0.0 to 29.0.1 in /apps/block_scout_web/assets +- [#5989](https://github.com/blockscout/blockscout/pull/5989) - Bump jquery from 3.6.0 to 3.6.1 in /apps/block_scout_web/assets +- [#5990](https://github.com/blockscout/blockscout/pull/5990) - Bump web3modal from 1.9.8 to 1.9.9 in /apps/block_scout_web/assets +- [#6004](https://github.com/blockscout/blockscout/pull/6004) - Bump luxon from 3.0.1 to 3.0.3 in /apps/block_scout_web/assets +- [#6005](https://github.com/blockscout/blockscout/pull/6005) - Bump ex_cldr from 2.33.1 to 2.33.2 +- [#6006](https://github.com/blockscout/blockscout/pull/6006) - Bump eslint from 8.22.0 to 8.23.0 in /apps/block_scout_web/assets +- [#6015](https://github.com/blockscout/blockscout/pull/6015) - Bump @fortawesome/fontawesome-free from 6.1.2 to 6.2.0 in /apps/block_scout_web/assets +- [#6021](https://github.com/blockscout/blockscout/pull/6021) - Bump sass from 1.54.5 to 1.54.7 in /apps/block_scout_web/assets +- [#6018](https://github.com/blockscout/blockscout/pull/6018) - Update chromedriver version +- [#5836](https://github.com/blockscout/blockscout/pull/5836) - Bump comeonin from 4.1.2 to 5.3.3 +- [#5869](https://github.com/blockscout/blockscout/pull/5869) - Bump reduce-reducers from 0.4.3 to 1.0.4 in /apps/block_scout_web/assets +- [#5919](https://github.com/blockscout/blockscout/pull/5919) - Bump floki from 0.32.1 to 0.33.1 +- [#5930](https://github.com/blockscout/blockscout/pull/5930) - Bump eslint from 8.21.0 to 8.22.0 in /apps/block_scout_web/assets +- [#5845](https://github.com/blockscout/blockscout/pull/5845) - Bump autoprefixer from 10.4.2 to 10.4.8 in /apps/block_scout_web/assets +- [#5877](https://github.com/blockscout/blockscout/pull/5877) - Bump eslint from 8.17.0 to 8.21.0 in /apps/block_scout_web/assets +- [#5875](https://github.com/blockscout/blockscout/pull/5875) - Bump sass from 1.49.8 to 1.54.3 in /apps/block_scout_web/assets +- [#5873](https://github.com/blockscout/blockscout/pull/5873) - Bump highlight.js from 11.4.0 to 11.6.0 in /apps/block_scout_web/assets +- [#5870](https://github.com/blockscout/blockscout/pull/5870) - Bump spandex_ecto from 0.6.2 to 0.7.0 +- [#5867](https://github.com/blockscout/blockscout/pull/5867) - Bump @babel/preset-env from 7.16.11 to 7.18.10 in /apps/block_scout_web/assets +- [#5876](https://github.com/blockscout/blockscout/pull/5876) - Bump bignumber.js from 9.0.2 to 9.1.0 in /apps/block_scout_web/assets +- [#5871](https://github.com/blockscout/blockscout/pull/5871) - Bump redux from 4.1.2 to 4.2.0 in /apps/block_scout_web/assets +- [#5868](https://github.com/blockscout/blockscout/pull/5868) - Bump ex_rlp from 0.5.3 to 0.5.4 +- [#5874](https://github.com/blockscout/blockscout/pull/5874) - Bump core-js from 3.20.3 to 3.24.1 in /apps/block_scout_web/assets +- [#5882](https://github.com/blockscout/blockscout/pull/5882) - Bump math from 0.3.1 to 0.7.0 +- [#5878](https://github.com/blockscout/blockscout/pull/5878) - Bump css-minimizer-webpack-plugin from 3.4.1 to 4.0.0 in /apps/block_scout_web/assets +- [#5883](https://github.com/blockscout/blockscout/pull/5883) - Bump postgrex from 0.15.10 to 0.15.13 +- [#5885](https://github.com/blockscout/blockscout/pull/5885) - Bump hammer from 6.0.0 to 6.1.0 +- [#5893](https://github.com/blockscout/blockscout/pull/5893) - Bump prometheus from 4.8.1 to 4.9.0 +- [#5892](https://github.com/blockscout/blockscout/pull/5892) - Bump babel-loader from 8.2.3 to 8.2.5 in /apps/block_scout_web/assets +- [#5890](https://github.com/blockscout/blockscout/pull/5890) - Bump sweetalert2 from 11.3.10 to 11.4.26 in /apps/block_scout_web/assets +- [#5889](https://github.com/blockscout/blockscout/pull/5889) - Bump sass from 1.54.3 to 1.54.4 in /apps/block_scout_web/assets +- [#5894](https://github.com/blockscout/blockscout/pull/5894) - Bump jest from 27.4.7 to 28.1.3 in /apps/block_scout_web/assets +- [#5865](https://github.com/blockscout/blockscout/pull/5865) - Bump timex from 3.7.1 to 3.7.9 +- [#5872](https://github.com/blockscout/blockscout/pull/5872) - Bump benchee from 0.13.2 to 0.99.0 +- [#5895](https://github.com/blockscout/blockscout/pull/5895) - Bump wallaby from 0.29.1 to 0.30.1 +- [#5905](https://github.com/blockscout/blockscout/pull/5905) - Bump absinthe from 1.6.5 to 1.6.8 +- [#5881](https://github.com/blockscout/blockscout/pull/5881) - Bump dataloader from 1.0.9 to 1.0.10 +- [#5909](https://github.com/blockscout/blockscout/pull/5909) - Bump junit_formatter from 3.3.0 to 3.3.1 +- [#5912](https://github.com/blockscout/blockscout/pull/5912) - Bump credo from 1.6.4 to 1.6.6 +- [#5911](https://github.com/blockscout/blockscout/pull/5911) - Bump absinthe_relay from 1.5.1 to 1.5.2 +- [#5915](https://github.com/blockscout/blockscout/pull/5915) - Bump flow from 0.15.0 to 1.2.0 +- [#5916](https://github.com/blockscout/blockscout/pull/5916) - Bump dialyxir from 1.1.0 to 1.2.0 +- [#5910](https://github.com/blockscout/blockscout/pull/5910) - Bump benchee from 0.99.0 to 1.1.0 +- [#5917](https://github.com/blockscout/blockscout/pull/5917) - Bump bypass from 1.0.0 to 2.1.0 +- [#5920](https://github.com/blockscout/blockscout/pull/5920) - Bump spandex_datadog from 1.1.0 to 1.2.0 +- [#5918](https://github.com/blockscout/blockscout/pull/5918) - Bump logger_file_backend from 0.0.12 to 0.0.13 +- [#5863](https://github.com/blockscout/blockscout/pull/5863) - Update Poison hex package +- [#5861](https://github.com/blockscout/blockscout/pull/5861) - Add cache for docker build +- [#5859](https://github.com/blockscout/blockscout/pull/5859) - Update ex_cldr hex packages +- [#5858](https://github.com/blockscout/blockscout/pull/5858) - Update CHANGELOG; revert update of css-loader; rename fontawesome icons selectors +- [#5811](https://github.com/blockscout/blockscout/pull/5811) - Bump chartjs-adapter-luxon from 1.1.0 to 1.2.0 in /apps/block_scout_web/assets +- [#5814](https://github.com/blockscout/blockscout/pull/5814) - Bump webpack from 5.69.1 to 5.74.0 in /apps/block_scout_web/assets +- [#5812](https://github.com/blockscout/blockscout/pull/5812) - Bump mini-css-extract-plugin from 2.5.3 to 2.6.1 in /apps/block_scout_web/assets +- [#5819](https://github.com/blockscout/blockscout/pull/5819) - Bump xss from 1.0.10 to 1.0.13 in /apps/block_scout_web/assets +- [#5818](https://github.com/blockscout/blockscout/pull/5818) - Bump @fortawesome/fontawesome-free from 6.0.0-beta3 to 6.1.2 in /apps/block_scout_web/assets +- [#5821](https://github.com/blockscout/blockscout/pull/5821) - Bump spandex from 3.0.3 to 3.1.0 +- [#5830](https://github.com/blockscout/blockscout/pull/5830) - Bump spandex_phoenix from 1.0.5 to 1.0.6 +- [#5825](https://github.com/blockscout/blockscout/pull/5825) - Bump postcss from 8.4.6 to 8.4.16 in /apps/block_scout_web/assets +- [#5816](https://github.com/blockscout/blockscout/pull/5816) - Bump webpack-cli from 4.9.2 to 4.10.0 in /apps/block_scout_web/assets +- [#5822](https://github.com/blockscout/blockscout/pull/5822) - Bump chart.js from 3.7.0 to 3.9.1 in /apps/block_scout_web/assets +- [#5829](https://github.com/blockscout/blockscout/pull/5829) - Bump mox from 0.5.2 to 1.0.2 +- [#5823](https://github.com/blockscout/blockscout/pull/5823) - Bump luxon from 2.4.0 to 3.0.1 in /apps/block_scout_web/assets +- [#5837](https://github.com/blockscout/blockscout/pull/5837) - Bump @walletconnect/web3-provider from 1.7.8 to 1.8.0 in /apps/block_scout_web/assets +- [#5840](https://github.com/blockscout/blockscout/pull/5840) - Bump web3modal from 1.9.5 to 1.9.8 in /apps/block_scout_web/assets +- [#5842](https://github.com/blockscout/blockscout/pull/5842) - Bump copy-webpack-plugin from 10.2.1 to 11.0.0 in /apps/block_scout_web/assets +- [#5835](https://github.com/blockscout/blockscout/pull/5835) - Bump tesla from 1.3.3 to 1.4.4 +- [#5841](https://github.com/blockscout/blockscout/pull/5841) - Bump sass-loader from 12.6.0 to 13.0.2 in /apps/block_scout_web/assets +- [#5844](https://github.com/blockscout/blockscout/pull/5844) - Bump postcss-loader from 6.2.1 to 7.0.1 in /apps/block_scout_web/assets +- [#5838](https://github.com/blockscout/blockscout/pull/5838) - Bump path-parser from 4.2.0 to 6.1.0 in /apps/block_scout_web/assets +- [#5843](https://github.com/blockscout/blockscout/pull/5843) - Bump @tarekraafat/autocomplete.js from 10.2.6 to 10.2.7 in /apps/block_scout_web/assets +- [#5834](https://github.com/blockscout/blockscout/pull/5834) - Bump clipboard from 2.0.9 to 2.0.11 in /apps/block_scout_web/assets +- [#5827](https://github.com/blockscout/blockscout/pull/5827) - Bump @babel/core from 7.16.12 to 7.18.10 in /apps/block_scout_web/assets +- [#5851](https://github.com/blockscout/blockscout/pull/5851) - Bump exvcr from 0.13.2 to 0.13.3 +- [#5824](https://github.com/blockscout/blockscout/pull/5824) - Bump ex_json_schema from 0.6.2 to 0.9.1 +- [#5849](https://github.com/blockscout/blockscout/pull/5849) - Bump gettext 0.18.2 -> 0.20.0 +- [#5806](https://github.com/blockscout/blockscout/pull/5806) - Update target Postgres version in Docker: 13 -> 14 + +## 4.1.7-beta + +### Features + +- [#5783](https://github.com/blockscout/blockscout/pull/5783) - Allow to setup multiple ranges of blocks to index + +### Fixes + +- [#5799](https://github.com/blockscout/blockscout/pull/5799) - Fix address_tokens_usd_sum function +- [#5798](https://github.com/blockscout/blockscout/pull/5798) - Copy explorer node_modules to result image +- [#5797](https://github.com/blockscout/blockscout/pull/5797) - Fix flickering token tooltip + +### Chore + +- [#5796](https://github.com/blockscout/blockscout/pull/5796) - Add job for e2e tests on every push to master + fix job "Merge 'master' to specific branch after release" + +## 4.1.6-beta + +### Features + +- [#5739](https://github.com/blockscout/blockscout/pull/5739) - Erigon archive node support +- [#5732](https://github.com/blockscout/blockscout/pull/5732) - Manage testnet label (right to the navbar logo) - [#5699](https://github.com/blockscout/blockscout/pull/5699) - Switch to basic (non-pro) API endpoint for Coingecko requests, if API key is not provided +- [#5542](https://github.com/blockscout/blockscout/pull/5542) - Add `jq` in docker image +- [#5345](https://github.com/blockscout/blockscout/pull/5345) - Graphql: add user-selected ordering to transactions for address query ### Fixes + +- [#5768](https://github.com/blockscout/blockscout/pull/5768) - Outstanding rows limit for missing blocks query (catchup fetcher) +- [#5737](https://github.com/blockscout/blockscout/pull/5737), [#5772](https://github.com/blockscout/blockscout/pull/5772) - Fix double requests; Fix token balances dropdown view +- [#5723](https://github.com/blockscout/blockscout/pull/5723) - Add nil clause for Data.to_string/1 +- [#5714](https://github.com/blockscout/blockscout/pull/5714) - Add clause for EthereumJSONRPC.Transaction.elixir_to_params/1 when gas_price is missing in the response - [#5697](https://github.com/blockscout/blockscout/pull/5697) - Gas price oracle: ignore gas price rounding for values less than 0.01 - [#5690](https://github.com/blockscout/blockscout/pull/5690) - Allow special characters for password in DB URL parser +- [#5778](https://github.com/blockscout/blockscout/pull/5778) - Allow hyphen in database name ### Chore +- [#5787](https://github.com/blockscout/blockscout/pull/5787) - Add job for merging master to specific branch after release +- [#5788](https://github.com/blockscout/blockscout/pull/5788) - Update Docker image on every push to master branch +- [#5736](https://github.com/blockscout/blockscout/pull/5736) - Remove obsolete network selector +- [#5730](https://github.com/blockscout/blockscout/pull/5730) - Add primary keys for DB tables where they do not exist - [#5703](https://github.com/blockscout/blockscout/pull/5703) - Remove bridged tokens functionality from Blockscout core - [#5700](https://github.com/blockscout/blockscout/pull/5700) - Remove Staking dapp logic from Blockscout core - [#5696](https://github.com/blockscout/blockscout/pull/5696) - Update .tool-versions - [#5695](https://github.com/blockscout/blockscout/pull/5695) - Decimal hex package update 1.9 -> 2.0 - [#5684](https://github.com/blockscout/blockscout/pull/5684) - Block import timings logs - ## 4.1.5-beta ### Features + - [#5667](https://github.com/blockscout/blockscout/pull/5667) - Address page: scroll to selected tab's data ### Fixes + - [#5680](https://github.com/blockscout/blockscout/pull/5680) - Fix broken token icons; Disable animation in lists; Fix doubled requests for some pages - [#5671](https://github.com/blockscout/blockscout/pull/5671) - Fix double requests for token exchange rates; Disable fetching `btc_value` by default (add `EXCHANGE_RATES_FETCH_BTC_VALUE` env variable); Add `CACHE_EXCHANGE_RATES_PERIOD` env variable - [#5676](https://github.com/blockscout/blockscout/pull/5676) - Fix wrong miner address shown for post EIP-1559 block for clique network ### Chore + - [#5679](https://github.com/blockscout/blockscout/pull/5679) - Optimize query in fetch_min_missing_block_cache function - [#5674](https://github.com/blockscout/blockscout/pull/5674) - Disable token holder refreshing - [#5661](https://github.com/blockscout/blockscout/pull/5661) - Fixes yaml syntax for boolean env variables in docker compose - ## 4.1.4-beta ### Features + - [#5656](https://github.com/blockscout/blockscout/pull/5656) - Gas price oracle - [#5613](https://github.com/blockscout/blockscout/pull/5613) - Exchange rates CoinMarketCap source module - [#5588](https://github.com/blockscout/blockscout/pull/5588) - Add broadcasting of coin balance +- [#5560](https://github.com/blockscout/blockscout/pull/5560) - Manual fetch benefeciaries - [#5479](https://github.com/blockscout/blockscout/pull/5479) - Remake of solidity verifier module; Verification UX improvements - [#5540](https://github.com/blockscout/blockscout/pull/5540) - Tx page: scroll to selected tab's data ### Fixes + - [#5647](https://github.com/blockscout/blockscout/pull/5647) - Add handling for invalid Sourcify response - [#5635](https://github.com/blockscout/blockscout/pull/5635) - Set CoinGecko source in exchange_rates_source function fix in case of token_bridge - [#5629](https://github.com/blockscout/blockscout/pull/5629) - Fix empty coin balance for empty address @@ -55,6 +449,7 @@ - [#5538](https://github.com/blockscout/blockscout/pull/5538) - Fix internal transaction's tile bug ### Chore + - [#5660](https://github.com/blockscout/blockscout/pull/5660) - Display txs count chart by default, disable price chart by default, add chart titles - [#5659](https://github.com/blockscout/blockscout/pull/5659) - Use chartjs-adapter-luxon instead chartjs-adapter-moment for charts - [#5651](https://github.com/blockscout/blockscout/pull/5651), [#5657](https://github.com/blockscout/blockscout/pull/5657) - Gnosis chain rebranded theme and generalization of chart legend colors definition @@ -64,10 +459,10 @@ - [#5543](https://github.com/blockscout/blockscout/pull/5543) - Increase max_restarts to 1_000 (from 3 by default) for explorer, block_scout_web supervisors - [#5536](https://github.com/blockscout/blockscout/pull/5536) - NPM audit fix - ## 4.1.3-beta ### Features + - [#5515](https://github.com/blockscout/blockscout/pull/5515) - Integrate ace editor to display contract sources - [#5505](https://github.com/blockscout/blockscout/pull/5505) - Manage debug_traceTransaction JSON RPC method timeout - [#5491](https://github.com/blockscout/blockscout/pull/5491) - Sequential blocks broadcast on the main page @@ -76,6 +471,7 @@ - [#5268](https://github.com/blockscout/blockscout/pull/5268), [#5313](https://github.com/blockscout/blockscout/pull/5313) - Contract names display improvement ### Fixes + - [#5528](https://github.com/blockscout/blockscout/pull/5528) - Token balances fetcher retry - [#5524](https://github.com/blockscout/blockscout/pull/5524) - ContractState module resistance to unresponsive archive node - [#5513](https://github.com/blockscout/blockscout/pull/5513) - Do not fill pending blocks ops with block numbers below TRACE_FIRST_BLOCK @@ -110,6 +506,7 @@ - [#5239](https://github.com/blockscout/blockscout/pull/5239) - Add accounting for block rewards in `getblockreward` api method ### Chore + - [#5506](https://github.com/blockscout/blockscout/pull/5506) - Refactor config files - [#5480](https://github.com/blockscout/blockscout/pull/5480) - Remove duplicate of balances_params_to_address_params function - [#5473](https://github.com/blockscout/blockscout/pull/5473) - Refactor daily coin balances fetcher @@ -139,10 +536,10 @@ - [#5260](https://github.com/blockscout/blockscout/pull/5260) - Makefile release task to prerelease and release task - [#5082](https://github.com/blockscout/blockscout/pull/5082) - Elixir 1.12 -> 1.13 - ## 4.1.2-beta ### Features + - [#5232](https://github.com/blockscout/blockscout/pull/5232) - Contract Read Page: Add functions overloading support - [#5220](https://github.com/blockscout/blockscout/pull/5220) - Add info about proxy contracts to api methods response - [#5200](https://github.com/blockscout/blockscout/pull/5200) - Docker-compose configuration @@ -151,6 +548,7 @@ - [#4690](https://github.com/blockscout/blockscout/pull/4690) - Improve pagination: introduce pagination with random access to pages; Integrate it to the Transactions List page ### Fixes + - [#5248](https://github.com/blockscout/blockscout/pull/5248) - Speedup query for getting verified smart-contract bytecode twin - [#5241](https://github.com/blockscout/blockscout/pull/5241) - Fix DB hostname Regex pattern - [#5216](https://github.com/blockscout/blockscout/pull/5216) - Add token-transfers-toggle.js to the `block_transaction/index.html.eex` @@ -169,6 +567,7 @@ - [#4862](https://github.com/blockscout/blockscout/pull/4862) - Fix internal transactions pagination ### Chore + - [#5230](https://github.com/blockscout/blockscout/pull/5230) - Contract verification forms refactoring - [#5227](https://github.com/blockscout/blockscout/pull/5227) - Major update of css-loader npm package - [#5226](https://github.com/blockscout/blockscout/pull/5226) - Update mini-css-extract-plugin, css-minimizer-webpack-plugin packages @@ -188,14 +587,15 @@ - [#5119](https://github.com/blockscout/blockscout/pull/5119) - Inventory controller refactoring - [#5118](https://github.com/blockscout/blockscout/pull/5118) - Fix top navigation template - ## 4.1.1-beta ### Features + - [#5090](https://github.com/blockscout/blockscout/pull/5090) - Allotted rate limit by IP - [#5080](https://github.com/blockscout/blockscout/pull/5080) - Allotted rate limit by a global API key ### Fixes + - [#5085](https://github.com/blockscout/blockscout/pull/5085) - Fix wallet style - [#5088](https://github.com/blockscout/blockscout/pull/5088) - Store address transactions/token transfers in the DB - [#5071](https://github.com/blockscout/blockscout/pull/5071) - Fix write page contract tuple input @@ -205,16 +605,17 @@ - [#5051](https://github.com/blockscout/blockscout/pull/5051) - Fix 500 response when ABI method was parsed as nil ### Chore + - [#5092](https://github.com/blockscout/blockscout/pull/5092) - Resolve vulnerable follow-redirects npm dep in ./apps/explorer - [#5091](https://github.com/blockscout/blockscout/pull/5091) - Refactor search page template - [#5081](https://github.com/blockscout/blockscout/pull/5081) - Add internal transactions fetcher disabled? config parameter - [#5063](https://github.com/blockscout/blockscout/pull/5063) - Resolve moderate NPM vulnerabilities with npm audit tool - [#5053](https://github.com/blockscout/blockscout/pull/5053) - Update ex_keccak lib - ## 4.1.0-beta ### Features + - [#5030](https://github.com/blockscout/blockscout/pull/5030) - API rate limiting - [#4924](https://github.com/blockscout/blockscout/pull/4924) - Add daily bytecode verifcation to prevent metamorphic contracts vulnerablity - [#4908](https://github.com/blockscout/blockscout/pull/4908) - Add verification via standard JSON input @@ -223,8 +624,9 @@ - [#4931](https://github.com/blockscout/blockscout/pull/4931) - Web3 modal with Wallet Connect for Write contract page and Staking Dapp ### Fixes + - [#5045](https://github.com/blockscout/blockscout/pull/5045) - Contracts interaction improvements -- [#5032](https://github.com/blockscout/blockscout/pull/5032) - Fix token transfer csv export +- [#5032](https://github.com/blockscout/blockscout/pull/5032) - Fix token transfer csv export - [#5020](https://github.com/blockscout/blockscout/pull/5020) - Token instance image display imrovement - [#5019](https://github.com/blockscout/blockscout/pull/5019) - Fix fetch_last_token_balance function termination - [#5011](https://github.com/blockscout/blockscout/pull/5011) - Fix `0x0` implementation address @@ -242,6 +644,7 @@ - [#4867](https://github.com/blockscout/blockscout/pull/4867) - Fix bug in quering contracts method and improve contracts interactions ### Chore + - [#5047](https://github.com/blockscout/blockscout/pull/5047) - At contract write use wei precision - [#5023](https://github.com/blockscout/blockscout/pull/5023) - Capability to leave an empty logo - [#5018](https://github.com/blockscout/blockscout/pull/5018) - Resolve npm vulnerabilities via npm audix fix @@ -250,10 +653,10 @@ - [#4983](https://github.com/blockscout/blockscout/pull/4983), [#5038](https://github.com/blockscout/blockscout/pull/5038) - Fix contract verification tests - [#4861](https://github.com/blockscout/blockscout/pull/4861) - Add separate column for token icons - ## 4.0.0-beta ### Features + - [#4807](https://github.com/blockscout/blockscout/pull/4807) - Added support for BeaconProxy pattern - [#4777](https://github.com/blockscout/blockscout/pull/4777), [#4791](https://github.com/blockscout/blockscout/pull/4791), [#4799](https://github.com/blockscout/blockscout/pull/4799), [#4847](https://github.com/blockscout/blockscout/pull/4847) - Added decoding revert reason - [#4776](https://github.com/blockscout/blockscout/pull/4776) - Added view for unsuccessfully fetched values from read functions @@ -277,6 +680,7 @@ - [#4579](https://github.com/blockscout/blockscout/pull/4579) - Write contract page: Resize inputs; Improve multiplier selector ### Fixes + - [#4857](https://github.com/blockscout/blockscout/pull/4857) - Fix `tx/raw-trace` Internal Server Error - [#4854](https://github.com/blockscout/blockscout/pull/4854) - Fix infinite gas usage count loading - [#4853](https://github.com/blockscout/blockscout/pull/4853) - Allow custom optimizations runs for contract verifications via API @@ -320,6 +724,7 @@ - [#4582](https://github.com/blockscout/blockscout/pull/4582) - Fix NaN input on write contract page ### Chore + - [#4876](https://github.com/blockscout/blockscout/pull/4876) - Add missing columns updates when INSERT ... ON CONFLICT DO UPDATE ... happens - [#4872](https://github.com/blockscout/blockscout/pull/4872) - Set explicit ascending order by hash in acquire transactions query of internal transactions import - [#4871](https://github.com/blockscout/blockscout/pull/4871) - Remove cumulative gas used update duplicate @@ -340,10 +745,10 @@ - [#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 - ## 3.7.3-beta ### Features + - [#4569](https://github.com/blockscout/blockscout/pull/4569) - Smart-Contract: remove comment with the submission date - [#4568](https://github.com/blockscout/blockscout/pull/4568) - TX page: Token transfer and minting section improvements - [#4540](https://github.com/blockscout/blockscout/pull/4540) - Allign copy buttons for `Block Details` and `Transaction Details` pages @@ -355,6 +760,7 @@ - [#4452](https://github.com/blockscout/blockscout/pull/4452) - Add names for smart-conrtact's function response ### Fixes + - [#4553](https://github.com/blockscout/blockscout/pull/4553) - Indexer performance update: skip genesis block in requesting of trace_block API endpoint - [#4544](https://github.com/blockscout/blockscout/pull/4544) - Indexer performance update: Add skip_metadata flag for token if indexer failed to get any of [name, symbol, decimals, totalSupply] - [#4542](https://github.com/blockscout/blockscout/pull/4542) - Indexer performance update: Deduplicate tokens in the indexer token transfers transformer @@ -367,12 +773,13 @@ - [#4488](https://github.com/blockscout/blockscout/pull/4488) - Tx page: handle empty to_address - [#4483](https://github.com/blockscout/blockscout/pull/4483) - Fix copy-paste typo in `token_transfers_counter.ex` - [#4473](https://github.com/blockscout/blockscout/pull/4473), [#4481](https://github.com/blockscout/blockscout/pull/4481) - Search autocomplete: fix for address/block/tx hash -- [#4472](https://github.com/blockscout/blockscout/pull/4472) - Search autocomplete: fix Cannot read property toLowerCase of undefined +- [#4472](https://github.com/blockscout/blockscout/pull/4472) - Search autocomplete: fix Cannot read property toLowerCase of undefined - [#4456](https://github.com/blockscout/blockscout/pull/4456) - URL encoding for NFT media files URLs - [#4453](https://github.com/blockscout/blockscout/pull/4453) - Unescape characters for string output type in the contract response - [#4401](https://github.com/blockscout/blockscout/pull/4401) - Fix displaying of token holders with the same amount ### Chore + - [#4550](https://github.com/blockscout/blockscout/pull/4550) - Update con_cache package to 1.0 - [#4523](https://github.com/blockscout/blockscout/pull/4523) - Change order of transations in block's view - [#4521](https://github.com/blockscout/blockscout/pull/4521) - Rewrite transaction page tooltips @@ -381,16 +788,17 @@ - [#4444](https://github.com/blockscout/blockscout/pull/4444) - Main page performance cumulative update - [#4439](https://github.com/blockscout/blockscout/pull/4439), - [#4465](https://github.com/blockscout/blockscout/pull/4465) - Fix revert response in contract's output - ## 3.7.2-beta ### Features + - [#4424](https://github.com/blockscout/blockscout/pull/4424) - Display search results categories - [#4423](https://github.com/blockscout/blockscout/pull/4423) - Add creation time of contract in the results of the search - [#4391](https://github.com/blockscout/blockscout/pull/4391) - Add batched transactions on the `address/{addressHash}/transactions` page - [#4353](https://github.com/blockscout/blockscout/pull/4353) - Added live-reload on the token holders page ### Fixes + - [#4437](https://github.com/blockscout/blockscout/pull/4437) - Fix `PendingTransactionsSanitizer` for non-consensus blocks - [#4430](https://github.com/blockscout/blockscout/pull/4430) - Fix current token balance on-demand fetcher - [#4429](https://github.com/blockscout/blockscout/pull/4429), [#4431](https://github.com/blockscout/blockscout/pull/4431) - Fix 500 response on `/tokens/{addressHash}/token-holders?type=JSON` when total supply is zero @@ -402,6 +810,7 @@ - [#4385](https://github.com/blockscout/blockscout/pull/4385) - Fix html template for transaction's input; Add copy text for tuples ### Chore + - [#4400](https://github.com/blockscout/blockscout/pull/4400) - Add "Token ID" label onto `tokens/.../instance/.../token-transfers` page - [#4398](https://github.com/blockscout/blockscout/pull/4398) - Speed up the transactions loading on the front-end - [#4384](https://github.com/blockscout/blockscout/pull/4384) - Fix Elixir version in `.tool-versions` @@ -409,27 +818,28 @@ - [#4371](https://github.com/blockscout/blockscout/pull/4371) - Place search outside of burger in mobile view - [#4355](https://github.com/blockscout/blockscout/pull/4355) - Do not redirect to 404 page with empty string in the search field - ## 3.7.1-beta ### Features + - [#4331](https://github.com/blockscout/blockscout/pull/4331) - Added support for partially verified contracts via [Sourcify](https://sourcify.dev) - [#4323](https://github.com/blockscout/blockscout/pull/4323) - Renamed Contract Byte Code, add Contract Creation Code on contract's page - [#4312](https://github.com/blockscout/blockscout/pull/4312) - Display pending transactions on address page - [#4299](https://github.com/blockscout/blockscout/pull/4299) - Added [Sourcify](https://sourcify.dev) verification API endpoint - [#4267](https://github.com/blockscout/blockscout/pull/4267) - Extend verification through [Sourcify](https://sourcify.dev) smart-contract verification: fetch smart contract metadata from Sourcify repo if it has been already verified there - [#4241](https://github.com/blockscout/blockscout/pull/4241) - Reload transactions on the main page without reloading of the whole page -- [#4218](https://github.com/blockscout/blockscout/pull/4218) - Hide long arrays in smart-contracts +- [#4218](https://github.com/blockscout/blockscout/pull/4218) - Hide long arrays in smart-contracts - [#4205](https://github.com/blockscout/blockscout/pull/4205) - Total transactions fees per day API endpoint - [#4158](https://github.com/blockscout/blockscout/pull/4158) - Calculate total fee per day - [#4067](https://github.com/blockscout/blockscout/pull/4067) - Display LP tokens USD value and custom metadata in tokens dropdown at address page ### Fixes + - [#4351](https://github.com/blockscout/blockscout/pull/4351) - Support effectiveGasPrice property in tx receipt (Geth specific) - [#4346](https://github.com/blockscout/blockscout/pull/4346) - Fix internal server error on raw-trace transaction page - [#4345](https://github.com/blockscout/blockscout/pull/4345) - Fix bug on validator's address transactions page(Support effectiveGasPrice property in receipt (geth specific)) - [#4342](https://github.com/blockscout/blockscout/pull/4342) - Remove dropped/replaced txs from address transactions page -- [#4320](https://github.com/blockscout/blockscout/pull/4320) - Fix absence of imported smart-contracts' source code in `getsourcecode` API method +- [#4320](https://github.com/blockscout/blockscout/pull/4320) - Fix absence of imported smart-contracts' source code in `getsourcecode` API method - [#4274](https://github.com/blockscout/blockscout/pull/4302) - Fix search token-autocomplete - [#4316](https://github.com/blockscout/blockscout/pull/4316) - Fix `/decompiled-contracts` bug - [#4310](https://github.com/blockscout/blockscout/pull/4310) - Fix logo URL redirection, set font-family defaults for chart.js @@ -450,6 +860,7 @@ - [#3888](https://github.com/blockscout/blockscout/pull/3888) - EIP-1967 contract proxy pattern detection fix ### Chore + - [#4315](https://github.com/blockscout/blockscout/pull/4315) - Replace node_modules/ with ~ in app.scss - [#4314](https://github.com/blockscout/blockscout/pull/4314) - Set infinite timeout for fetch_min_missing_block_cache method DB query - [#4300](https://github.com/blockscout/blockscout/pull/4300) - Remove clear_build.sh script @@ -465,10 +876,10 @@ - [#3893](https://github.com/blockscout/blockscout/pull/3893) - Add left/right paddings in tx tile - [#3870](https://github.com/blockscout/blockscout/pull/3870) - Manage token balance on-demand fetcher threshold via env var - ## 3.7.0-beta ### Features + - [#3858](https://github.com/blockscout/blockscout/pull/3858) - Integration with Sourcify - [#3834](https://github.com/blockscout/blockscout/pull/3834) - Method name in tx tile - [#3792](https://github.com/blockscout/blockscout/pull/3792) - Cancel pending transaction @@ -477,6 +888,7 @@ - [#3750](https://github.com/blockscout/blockscout/pull/3750) - getblocknobytime block module API endpoint ### Fixes + - [#3835](https://github.com/blockscout/blockscout/pull/3835) - Fix getTokenHolders API endpoint pagination - [#3787](https://github.com/blockscout/blockscout/pull/3787) - Improve tokens list elements display - [#3785](https://github.com/blockscout/blockscout/pull/3785) - Fix for write contract functionality: false and 0 boolean inputs are parsed as true @@ -486,6 +898,7 @@ - [#3748](https://github.com/blockscout/blockscout/pull/3748) - Skip null topics in eth_getLogs API endpoint ### Chore + - [#3831](https://github.com/blockscout/blockscout/pull/3831) - Process type field in eth_getTransactionReceipt response - [#3802](https://github.com/blockscout/blockscout/pull/3802) - Extend Become a Candidate popup in Staking DApp - [#3801](https://github.com/blockscout/blockscout/pull/3801) - Poison package update @@ -493,10 +906,10 @@ - [#3789](https://github.com/blockscout/blockscout/pull/3789) - Update repo organization - [#3788](https://github.com/blockscout/blockscout/pull/3788) - Update fontawesome NPM package - ## 3.6.0-beta ### Features + - [#3743](https://github.com/blockscout/blockscout/pull/3743) - Minimal proxy pattern support (EIP-1167) - [#3722](https://github.com/blockscout/blockscout/pull/3722) - Allow double quotes for (u)int arrays inputs during contract interaction - [#3694](https://github.com/blockscout/blockscout/pull/3694) - LP tokens total liquidity @@ -511,6 +924,7 @@ - [#3564](https://github.com/blockscout/blockscout/pull/3564) - Staking welcome message ### Fixes + - [#3742](https://github.com/blockscout/blockscout/pull/3742) - Fix Sushiswap LP tokens custom metadata fetcher: bytes(n) symbol and name support - [#3741](https://github.com/blockscout/blockscout/pull/3741) - Contract reader fix when there are multiple input params including an array type - [#3735](https://github.com/blockscout/blockscout/pull/3735) - Token balance on demand fetcher memory leak fix @@ -538,6 +952,7 @@ - [#3577](https://github.com/blockscout/blockscout/pull/3577) - Eliminate GraphiQL page XSS attack ### Chore + - [#3745](https://github.com/blockscout/blockscout/pull/3745) - Refactor and optimize Staking DApp - [#3744](https://github.com/blockscout/blockscout/pull/3744) - Update Mix packages: timex, hackney, tzdata certifi - [#3736](https://github.com/blockscout/blockscout/pull/3736), [#3739](https://github.com/blockscout/blockscout/pull/3739) - Contract writer: Fix sending a transaction with tuple input type @@ -567,25 +982,27 @@ - [#3567](https://github.com/blockscout/blockscout/pull/3567) - Force to show filter at the page where filtered items list is empty - [#3565](https://github.com/blockscout/blockscout/pull/3565) - Staking dapp: unhealthy state alert message - ## 3.5.1-beta ### Features + - [#3558](https://github.com/blockscout/blockscout/pull/3558) - Focus to search field with a forward slash key -- [#3541](https://github.com/blockscout/blockscout/pull/3541) - Staking dapp stats: total number of delegators, total staked amount +- [#3541](https://github.com/blockscout/blockscout/pull/3541) - Staking dapp stats: total number of delegators, total staked amount - [#3540](https://github.com/blockscout/blockscout/pull/3540) - Apply DarkForest custom theme to NFT instances ### Fixes + - [#3551](https://github.com/blockscout/blockscout/pull/3551) - Fix contract's method's output of tuple type ### Chore + - [#3557](https://github.com/blockscout/blockscout/pull/3557) - Single Staking menu - [#3540](https://github.com/blockscout/blockscout/pull/3540), [#3545](https://github.com/blockscout/blockscout/pull/3545) - Support different versions of DarkForest (0.4 - 0.5) - ## 3.5.0-beta ### Features + - [#3536](https://github.com/blockscout/blockscout/pull/3536) - Revert reason in the result of contract's method call - [#3532](https://github.com/blockscout/blockscout/pull/3532) - Contract interaction: an easy setting of precision for integer input - [#3531](https://github.com/blockscout/blockscout/pull/3531) - Allow double quotes in input data of contract methods @@ -596,6 +1013,7 @@ - [#3462](https://github.com/blockscout/blockscout/pull/3462) - Display price for bridged tokens ### Fixes + - [#3535](https://github.com/blockscout/blockscout/pull/3535) - Improve speed of tokens dropdown loading at owner address page - [#3530](https://github.com/blockscout/blockscout/pull/3530) - Allow trailing/leading whitespaces for inputs for contract read methods - [#3526](https://github.com/blockscout/blockscout/pull/3526) - Order staking pools @@ -621,6 +1039,7 @@ - [#3457](https://github.com/blockscout/blockscout/pull/3457) - Fix doubled token transfer on block's page if block has reorg ### Chore + - [#3500](https://github.com/blockscout/blockscout/pull/3500) - Update solc version in explorer folder - [#3498](https://github.com/blockscout/blockscout/pull/3498) - Make Staking DApp work with transferAndCall function - [#3496](https://github.com/blockscout/blockscout/pull/3496) - Rollback websocket_client module to 1.3.0 @@ -633,10 +1052,10 @@ - [#3467](https://github.com/blockscout/blockscout/pull/3467) - NodeJS engine upgrade up to 14 - [#3460](https://github.com/blockscout/blockscout/pull/3460) - Update Staking DApp scripts due to MetaMask breaking changes - ## 3.4.0-beta ### Features + - [#3442](https://github.com/blockscout/blockscout/pull/3442) - Constructor arguments autodetection in API verify endpoint - [#3435](https://github.com/blockscout/blockscout/pull/3435) - Token transfers counter cache - [#3420](https://github.com/blockscout/blockscout/pull/3420) - Enable read/write proxy tabs for Gnosis safe proxy contract @@ -652,6 +1071,7 @@ - [#3330](https://github.com/blockscout/blockscout/pull/3330) - Caching of address transactions counter, remove query 10_000 rows limit ### Fixes + - [#3449](https://github.com/blockscout/blockscout/pull/3449) - Correct avg time calculation - [#3443](https://github.com/blockscout/blockscout/pull/3443) - Improve blocks handling in Staking DApp - [#3440](https://github.com/blockscout/blockscout/pull/3440) - Rewrite missing blocks range query @@ -686,6 +1106,7 @@ - [#3335](https://github.com/blockscout/blockscout/pull/3335) - MarketCap calculation: check that ETS tables exist before inserting new data or lookup from the table ### Chore + - [#5240](https://github.com/blockscout/blockscout/pull/5240) - Managing invalidation of address coin balance cache - [#3450](https://github.com/blockscout/blockscout/pull/3450) - Replace window.web3 with window.ethereum - [#3446](https://github.com/blockscout/blockscout/pull/3446), [#3448](https://github.com/blockscout/blockscout/pull/3448) - Set infinity timeout and increase cache invalidation period for counters @@ -702,10 +1123,10 @@ - [#3366](https://github.com/blockscout/blockscout/pull/3366) - Stabilize tests execution in Github Actions CI - [#3343](https://github.com/blockscout/blockscout/pull/3343) - Make (Bridged) Tokens' list page's header more compact - ## 3.3.3-beta ### Features + - [#3320](https://github.com/blockscout/blockscout/pull/3320) - Bridged tokens from AMB extensions support - [#3311](https://github.com/blockscout/blockscout/pull/3311) - List of addresses with restricted access option - [#3293](https://github.com/blockscout/blockscout/pull/3293) - Composite market cap for xDai: TokenBridge + OmniBridge @@ -718,6 +1139,7 @@ - [#3261](https://github.com/blockscout/blockscout/pull/3261) - Bridged tokens table ### Fixes + - [#3323](https://github.com/blockscout/blockscout/pull/3323) - Fix logs list API endpoint response - [#3319](https://github.com/blockscout/blockscout/pull/3319) - Eliminate horizontal scroll - [#3314](https://github.com/blockscout/blockscout/pull/3314) - Handle nil values from response of CoinGecko price API @@ -736,6 +1158,7 @@ - [#3256](https://github.com/blockscout/blockscout/pull/3256) - Fix for invisible validator address at block page and wrong alert text color at xDai ### Chore + - [#3327](https://github.com/blockscout/blockscout/pull/3327) - Handle various indexer fetchers errors in setup with non-archive node - [#3325](https://github.com/blockscout/blockscout/pull/3325) - Dark theme improvements - [#3316](https://github.com/blockscout/blockscout/pull/3316), [#3317](https://github.com/blockscout/blockscout/pull/3317) - xDai smile logo @@ -747,10 +1170,10 @@ - [#3260](https://github.com/blockscout/blockscout/pull/3260) - Update NPM dependencies to fix known vulnerabilities - [#3258](https://github.com/blockscout/blockscout/pull/3258) - Token transfer: check that block exists before retrieving timestamp - ## 3.3.2-beta ### Features + - [#3252](https://github.com/blockscout/blockscout/pull/3252) - Gas price at the main page - [#3239](https://github.com/blockscout/blockscout/pull/3239) - Hide address page tabs if no items - [#3236](https://github.com/blockscout/blockscout/pull/3236) - Easy verification of contracts which has verified twins (the same bytecode) @@ -758,6 +1181,7 @@ - [#3224](https://github.com/blockscout/blockscout/pull/3224) - Top tokens page ### Fixes + - [#3249](https://github.com/blockscout/blockscout/pull/3249) - Fix incorrect ABI decoding of address in tuple output - [#3237](https://github.com/blockscout/blockscout/pull/3237) - Refine contract method signature detection for read/write feature - [#3235](https://github.com/blockscout/blockscout/pull/3235) - Fix coin supply api edpoint @@ -769,15 +1193,16 @@ - [#3220](https://github.com/blockscout/blockscout/pull/3220) - Allow interaction with navbar menu at block-not-found page ### Chore + - [#3326](https://github.com/blockscout/blockscout/pull/3326) - Chart smooth lines - [#3250](https://github.com/blockscout/blockscout/pull/3250) - Eliminate occurrences of obsolete env variable ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT - [#3240](https://github.com/blockscout/blockscout/pull/3240), [#3251](https://github.com/blockscout/blockscout/pull/3251) - various CSS imroving - [f3a720](https://github.com/blockscout/blockscout/commit/2dd909c10a79b0bf4b7541a486be114152f3a720) - Make wobserver optional - ## 3.3.1-beta ### Features + - [#3216](https://github.com/blockscout/blockscout/pull/3216) - Display new token transfers at token page and address page without refreshing the page - [#3199](https://github.com/blockscout/blockscout/pull/3199) - Show compilation error at contract verification - [#3193](https://github.com/blockscout/blockscout/pull/3193) - Raw trace copy button @@ -785,6 +1210,7 @@ - [#3145](https://github.com/blockscout/blockscout/pull/3145) - Pending txs per address API endpoint ### Fixes + - [#3219](https://github.com/blockscout/blockscout/pull/3219) - Fix revert reason message detection - [#3215](https://github.com/blockscout/blockscout/pull/3215) - Coveralls in CI through Github Actions - [#3214](https://github.com/blockscout/blockscout/pull/3214) - Fix current token balances fetcher @@ -802,6 +1228,7 @@ - [#3178](https://github.com/blockscout/blockscout/pull/3178) - Fix unavailable navbar menu when read/write proxy tab is active ### Chore + - [#3212](https://github.com/blockscout/blockscout/pull/3212) - GitHub actions CI config - [#3210](https://github.com/blockscout/blockscout/pull/3210) - Update Phoenix up to 1.4.17 - [#3206](https://github.com/blockscout/blockscout/pull/3206) - Update Elixir version: 1.10.2 -> 1.10.3 @@ -809,10 +1236,10 @@ - [#3180](https://github.com/blockscout/blockscout/pull/3180) - Return correct status in verify API endpoint if contract verified - [#3180](https://github.com/blockscout/blockscout/pull/3180) - Remove Kovan from the list of default chains - ## 3.3.0-beta ### Features + - [#3174](https://github.com/blockscout/blockscout/pull/3174) - EIP-1967 support: transparent proxy pattern - [#3173](https://github.com/blockscout/blockscout/pull/3173) - Display implementation address at read/write proxy tabs - [#3171](https://github.com/blockscout/blockscout/pull/3171) - Import accounts/contracts/balances from Geth genesis.json @@ -821,15 +1248,16 @@ - [#3157](https://github.com/blockscout/blockscout/pull/3157) - Read methods of implementation on proxy contract ### Fixes + - [#3168](https://github.com/blockscout/blockscout/pull/3168) - Eliminate internal server error at /accounts page with token-bridge type of supply and inexistent bridge contracts - [#3169](https://github.com/blockscout/blockscout/pull/3169) - Fix for verification of contracts defined in genesis block ### Chore - ## 3.2.0-beta ### Features + - [#3154](https://github.com/blockscout/blockscout/pull/3154) - Support of Hyperledger Besu client - [#3153](https://github.com/blockscout/blockscout/pull/3153) - Proxy contracts: logs decoding using implementation ABI - [#3153](https://github.com/blockscout/blockscout/pull/3153) - Proxy contracts: methods decoding using implementation ABI @@ -838,15 +1266,17 @@ ### Fixes ### Chore -- [#3152](https://github.com/blockscout/blockscout/pull/3152) - Fix contract compilation tests for old versions of compiler +- [#3152](https://github.com/blockscout/blockscout/pull/3152) - Fix contract compilation tests for old versions of compiler ## 3.1.3-beta ### Features + - [#3125](https://github.com/blockscout/blockscout/pull/3125) - Availability to configure a number of days to consider at coin balance history chart via environment variable ### Fixes + - [#3146](https://github.com/blockscout/blockscout/pull/3146) - Fix coin balance history page: order of items, fix if no balance changes - [#3142](https://github.com/blockscout/blockscout/pull/3142) - Speed-up last coin balance timestamp query (coin balance history page performance improvement) - [#3140](https://github.com/blockscout/blockscout/pull/3140) - Fix performance of the balance changing history list loading @@ -866,14 +1296,15 @@ - [#3106](https://github.com/blockscout/blockscout/pull/3106), [#3115](https://github.com/blockscout/blockscout/pull/3115) - Fix verification of contracts, created from factory (from internal transaction) ### Chore + - [#3137](https://github.com/blockscout/blockscout/pull/3137) - RSK Papyrus Release v2.0.1 hardfork: cumulativeDifficulty - [#3134](https://github.com/blockscout/blockscout/pull/3134) - Get last value of fetched coinsupply API endpoint from DB if cache is empty - [#3124](https://github.com/blockscout/blockscout/pull/3124) - Display upper border for tx speed if the value cannot be calculated - ## 3.1.2-beta ### Features + - [#3089](https://github.com/blockscout/blockscout/pull/3089) - CoinGecko API coin id environment variable - [#3069](https://github.com/blockscout/blockscout/pull/3069) - Make a link to address page on decoded constructor argument of address type - [#3067](https://github.com/blockscout/blockscout/pull/3067) - Show proper title of the tile or container for token burnings/mintings instead of "Token Transfer" @@ -881,6 +1312,7 @@ - [#3065](https://github.com/blockscout/blockscout/pull/3065) - Transactions history chart ### Fixes + - [#3097](https://github.com/blockscout/blockscout/pull/3097) - Fix contract reader decoding - [#3095](https://github.com/blockscout/blockscout/pull/3095) - Fix constructor arguments decoding - [#3092](https://github.com/blockscout/blockscout/pull/3092) - Contract verification: constructor arguments search search refinement @@ -893,35 +1325,38 @@ - [#2756](https://github.com/blockscout/blockscout/pull/2756) - Improve subquery joins ### Chore + - [#3100](https://github.com/blockscout/blockscout/pull/3100) - Update npm packages - [#3099](https://github.com/blockscout/blockscout/pull/3099) - Remove pending txs cache - [#3093](https://github.com/blockscout/blockscout/pull/3093) - Extend list of env vars for Docker setup - [#3084](https://github.com/blockscout/blockscout/pull/3084) - Bump Elixir version 1.10.2 - [#3079](https://github.com/blockscout/blockscout/pull/3079) - Extend optionality of websockets to Geth - ## 3.1.1-beta ### Features + - [#3058](https://github.com/blockscout/blockscout/pull/3058) - Searching by verified contract name ### Fixes + - [#3053](https://github.com/blockscout/blockscout/pull/3053) - Fix ABI decoding in contracts methods, logs (migrate to ex_abi 0.3.0) - [#3044](https://github.com/blockscout/blockscout/pull/3044) - Prevent division by zero on /accounts page - [#3043](https://github.com/blockscout/blockscout/pull/3043) - Extract host name for split couple of indexer and web app - [#3042](https://github.com/blockscout/blockscout/pull/3042) - Speedup pending txs list query - [#2944](https://github.com/blockscout/blockscout/pull/2944), [#3046](https://github.com/blockscout/blockscout/pull/3046) - Split js logic into multiple files - ## 3.1.0-beta ### Features + - [#3013](https://github.com/blockscout/blockscout/pull/3013), [#3026](https://github.com/blockscout/blockscout/pull/3026), [#3031](https://github.com/blockscout/blockscout/pull/3031) - Raw trace of transaction on-demand - [#3000](https://github.com/blockscout/blockscout/pull/3000) - Get rid of storing of first trace for all types of transactions for Parity variant - [#2875](https://github.com/blockscout/blockscout/pull/2875) - Save contract code from Parity genesis file - [#2834](https://github.com/blockscout/blockscout/pull/2834), [#3009](https://github.com/blockscout/blockscout/pull/3009), [#3014](https://github.com/blockscout/blockscout/pull/3014), [#3033](https://github.com/blockscout/blockscout/pull/3033) - always redirect to checksummed hash ### Fixes + - [#3037](https://github.com/blockscout/blockscout/pull/3037) - Make buttons color at verification page consistent - [#3034](https://github.com/blockscout/blockscout/pull/3034) - Support stateMutability=view to define reading functions in smart-contracts - [#3029](https://github.com/blockscout/blockscout/pull/3029) - Fix transactions and blocks appearance on the main page @@ -945,20 +1380,22 @@ - [#2883](https://github.com/blockscout/blockscout/pull/2883) - Fix long contracts names ### Chore + - [#3032](https://github.com/blockscout/blockscout/pull/3032) - Remove indexing status alert for Ganache variant - [#3030](https://github.com/blockscout/blockscout/pull/3030) - Remove default websockets URL from config - [#2995](https://github.com/blockscout/blockscout/pull/2995) - Support API_PATH env var in Docker file - ## 3.0.0-beta ### Features + - [#2835](https://github.com/blockscout/blockscout/pull/2835), [#2871](https://github.com/blockscout/blockscout/pull/2871), [#2872](https://github.com/blockscout/blockscout/pull/2872), [#2886](https://github.com/blockscout/blockscout/pull/2886), [#2925](https://github.com/blockscout/blockscout/pull/2925), [#2936](https://github.com/blockscout/blockscout/pull/2936), [#2949](https://github.com/blockscout/blockscout/pull/2949), [#2940](https://github.com/blockscout/blockscout/pull/2940), [#2958](https://github.com/blockscout/blockscout/pull/2958) - Add "block_hash" to logs, token_transfers and internal transactions and "pending blocks operations" approach - [#2975](https://github.com/blockscout/blockscout/pull/2975) - Refine UX of contracts verification - [#2926](https://github.com/blockscout/blockscout/pull/2926) - API endpoint: sum balances except burnt address - [#2918](https://github.com/blockscout/blockscout/pull/2918) - Add tokenID for tokentx API action explicitly ### Fixes + - [#2969](https://github.com/blockscout/blockscout/pull/2969) - Fix contract constructor require msg appearance in constructor arguments encoded view - [#2964](https://github.com/blockscout/blockscout/pull/2964) - Fix bug in skipping of constructor arguments in contract verification - [#2961](https://github.com/blockscout/blockscout/pull/2961) - Add a guard that addresses is enum in `values` function in `read contract` page @@ -982,6 +1419,7 @@ - [#2887](https://github.com/blockscout/blockscout/pull/2887) - increase chart loading speed ### Chore + - [#2959](https://github.com/blockscout/blockscout/pull/2959) - Remove logs from test folder too in the cleaning script - [#2954](https://github.com/blockscout/blockscout/pull/2954) - Upgrade absinthe and ecto deps - [#2947](https://github.com/blockscout/blockscout/pull/2947) - Upgrade Circle CI postgres Docker image @@ -990,10 +1428,10 @@ - [#2896](https://github.com/blockscout/blockscout/pull/2896) - Disable Parity websockets tests - [#2873](https://github.com/blockscout/blockscout/pull/2873) - bump elixir to 1.9.4 - ## 2.1.1-beta ### Features + - [#2862](https://github.com/blockscout/blockscout/pull/2862) - Coin total supply from DB API endpoint - [#2857](https://github.com/blockscout/blockscout/pull/2857) - Extend getsourcecode API view with new output fields - [#2822](https://github.com/blockscout/blockscout/pull/2822) - Estimated address count on the main page, if cache is empty @@ -1004,6 +1442,7 @@ - [#2449](https://github.com/blockscout/blockscout/pull/2449) - add ability to send notification events through postgres notify ### Fixes + - [#2864](https://github.com/blockscout/blockscout/pull/2864) - add token instance metadata type check - [#2855](https://github.com/blockscout/blockscout/pull/2855) - Fix favicons load - [#2854](https://github.com/blockscout/blockscout/pull/2854) - Fix all npm vulnerabilities @@ -1022,6 +1461,7 @@ - [#2690](https://github.com/blockscout/blockscout/pull/2690) - do not stich json rpc config into module for net version cache ### Chore + - [#2878](https://github.com/blockscout/blockscout/pull/2878) - Decrease loaders showing delay on the main page - [#2859](https://github.com/blockscout/blockscout/pull/2859) - Add eth_blockNumber API endpoint to eth_rpc section - [#2846](https://github.com/blockscout/blockscout/pull/2846) - Remove networks images preload @@ -1035,10 +1475,10 @@ - [#2805](https://github.com/blockscout/blockscout/pull/2805) - Update supported chains default option - [#2801](https://github.com/blockscout/blockscout/pull/2801) - remove unused clause in address_to_unique_tokens query - ## 2.1.0-beta ### Features + - [#2776](https://github.com/blockscout/blockscout/pull/2776) - fetch token counters async - [#2772](https://github.com/blockscout/blockscout/pull/2772) - add token instance images to the token inventory tab - [#2733](https://github.com/blockscout/blockscout/pull/2733) - Add cache for first page of uncles @@ -1057,6 +1497,7 @@ - [#2470](https://github.com/blockscout/blockscout/pull/2470) - Allow Realtime Fetcher to wait for small skips ### Fixes + - [#4325](https://github.com/blockscout/blockscout/pull/4325) - Fix search on `/tokens` page - [#2793](https://github.com/blockscout/blockscout/pull/2793) - Hide "We are indexing this chain right now. Some of the counts may be inaccurate" banner if no txs in blockchain - [#2779](https://github.com/blockscout/blockscout/pull/2779) - fix fetching `latin1` encoded data @@ -1098,6 +1539,7 @@ fixed menu hovers in dark mode desktop view - [#2738](https://github.com/blockscout/blockscout/pull/2738) - do not fail block `internal_transactions_indexed_at` field update ### Chore + - [#2797](https://github.com/blockscout/blockscout/pull/2797) - Return old style menu - [#2796](https://github.com/blockscout/blockscout/pull/2796) - Optimize all images with ImageOptim - [#2794](https://github.com/blockscout/blockscout/pull/2786) - update hosted versions in readme @@ -1111,10 +1553,10 @@ fixed menu hovers in dark mode desktop view - [#2723](https://github.com/blockscout/blockscout/pull/2723) - get rid of ex_json_schema warnings - [#2740](https://github.com/blockscout/blockscout/pull/2740) - add verify contract rpc doc - ## 2.0.4-beta ### Features + - [#2636](https://github.com/blockscout/blockscout/pull/2636) - Execute all address' transactions page queries in parallel - [#2596](https://github.com/blockscout/blockscout/pull/2596) - support AuRa's empty step reward type - [#2588](https://github.com/blockscout/blockscout/pull/2588) - add verification submission comment @@ -1126,6 +1568,7 @@ fixed menu hovers in dark mode desktop view - [#2497](https://github.com/blockscout/blockscout/pull/2497) - Add generic Ordered Cache behaviour and implementation ### Fixes + - [#2659](https://github.com/blockscout/blockscout/pull/2659) - Multipurpose front-end part update - [#2640](https://github.com/blockscout/blockscout/pull/2640) - SVG network icons - [#2635](https://github.com/blockscout/blockscout/pull/2635) - optimize ERC721 inventory query @@ -1147,6 +1590,7 @@ fixed menu hovers in dark mode desktop view - [#2468](https://github.com/blockscout/blockscout/pull/2468) - fix confirmations for non consensus blocks ### Chore + - [#2662](https://github.com/blockscout/blockscout/pull/2662) - fetch coin gecko id based on the coin symbol - [#2646](https://github.com/blockscout/blockscout/pull/2646) - Added Xerom to list of Additional Chains using BlockScout - [#2634](https://github.com/blockscout/blockscout/pull/2634) - add Lukso to networks dropdown @@ -1158,10 +1602,10 @@ fixed menu hovers in dark mode desktop view - [#2574](https://github.com/blockscout/blockscout/pull/2574) - limit request body in json rpc error - [#2566](https://github.com/blockscout/blockscout/pull/2566) - upgrade absinthe phoenix - ## 2.0.3-beta ### Features + - [#2433](https://github.com/blockscout/blockscout/pull/2433) - Add a functionality to try Eth RPC methods in the documentation - [#2529](https://github.com/blockscout/blockscout/pull/2529) - show both eth value and token transfers on transaction overview page - [#2376](https://github.com/blockscout/blockscout/pull/2376) - Split API and WebApp routes @@ -1171,6 +1615,7 @@ fixed menu hovers in dark mode desktop view - [#2403](https://github.com/blockscout/blockscout/pull/2403) - Return gasPrice field at the result of gettxinfo method ### Fixes + - [#2562](https://github.com/blockscout/blockscout/pull/2562) - Fix dark theme flickering - [#2560](https://github.com/blockscout/blockscout/pull/2560) - fix slash before not empty path in docs - [#2559](https://github.com/blockscout/blockscout/pull/2559) - fix rsk total supply for empty exchange rate @@ -1204,6 +1649,7 @@ fixed menu hovers in dark mode desktop view - [#2551](https://github.com/blockscout/blockscout/pull/2551) - Correctly handle dynamically created Bootstrap tooltips ### Chore + - [#2554](https://github.com/blockscout/blockscout/pull/2554) - remove extra slash for endpoint url in docs - [#2552](https://github.com/blockscout/blockscout/pull/2552) - remove brackets for token holders percentage - [#2507](https://github.com/blockscout/blockscout/pull/2507) - update minor version of ecto, ex_machina, phoenix_live_reload @@ -1220,10 +1666,10 @@ fixed menu hovers in dark mode desktop view - [#2402](https://github.com/blockscout/blockscout/pull/2402) - bump otp version to 22.0 - [#2373](https://github.com/blockscout/blockscout/pull/2373) - Add script to validate internal_transactions constraint for large DBs - ## 2.0.2-beta ### Features + - [#2412](https://github.com/blockscout/blockscout/pull/2412) - dark theme - [#2399](https://github.com/blockscout/blockscout/pull/2399) - decode verified smart contract's logs - [#2391](https://github.com/blockscout/blockscout/pull/2391) - Controllers Improvements @@ -1236,6 +1682,7 @@ fixed menu hovers in dark mode desktop view - [#2324](https://github.com/blockscout/blockscout/pull/2324) - set timeout for loading message on the main page ### Fixes + - [#2421](https://github.com/blockscout/blockscout/pull/2421) - Fix hiding of loader for txs on the main page - [#2420](https://github.com/blockscout/blockscout/pull/2420) - fetch data from cache in healthy endpoint - [#2416](https://github.com/blockscout/blockscout/pull/2416) - Fix "page not found" handling in the router @@ -1263,6 +1710,7 @@ fixed menu hovers in dark mode desktop view - [#2326](https://github.com/blockscout/blockscout/pull/2326) - fix nested constructor arguments ### Chore + - [#2422](https://github.com/blockscout/blockscout/pull/2422) - check if address_id is binary in token_transfers_csv endpoint - [#2418](https://github.com/blockscout/blockscout/pull/2418) - Remove parentheses in market cap percentage - [#2401](https://github.com/blockscout/blockscout/pull/2401) - add ENV vars to manage updating period of average block time and market history cache @@ -1277,10 +1725,10 @@ fixed menu hovers in dark mode desktop view - [#2293](https://github.com/blockscout/blockscout/pull/2293) - remove request idle timeout configuration - [#2255](https://github.com/blockscout/blockscout/pull/2255) - bump elixir version to 1.9.0 - ## 2.0.1-beta ### Features + - [#2283](https://github.com/blockscout/blockscout/pull/2283) - Add transactions cache - [#2182](https://github.com/blockscout/blockscout/pull/2182) - add market history cache - [#2109](https://github.com/blockscout/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch @@ -1295,6 +1743,7 @@ fixed menu hovers in dark mode desktop view - [#2266](https://github.com/blockscout/blockscout/pull/2266) - allow excluding uncles from average block time calculation ### Fixes + - [#2290](https://github.com/blockscout/blockscout/pull/2290) - Add eth_get_balance.json to AddressView's render - [#2286](https://github.com/blockscout/blockscout/pull/2286) - banner stats issues on sm resolutions, transactions title issue - [#2284](https://github.com/blockscout/blockscout/pull/2284) - add 404 status for not existing pages @@ -1360,15 +1809,16 @@ fixed menu hovers in dark mode desktop view - [#2276](https://github.com/blockscout/blockscout/pull/2276) - remove port in docs ### Chore + - [#2127](https://github.com/blockscout/blockscout/pull/2127) - use previouse chromedriver version - [#2118](https://github.com/blockscout/blockscout/pull/2118) - show only the last decompiled contract - [#2255](https://github.com/blockscout/blockscout/pull/2255) - upgrade elixir version to 1.9.0 - [#2256](https://github.com/blockscout/blockscout/pull/2256) - use the latest version of chromedriver - ## 2.0.0-beta ### Features + - [#2044](https://github.com/blockscout/blockscout/pull/2044) - New network selector. - [#2091](https://github.com/blockscout/blockscout/pull/2091) - Added "Question" modal. - [#1963](https://github.com/blockscout/blockscout/pull/1963), [#1959](https://github.com/blockscout/blockscout/pull/1959), [#1948](https://github.com/blockscout/blockscout/pull/1948), [#1936](https://github.com/blockscout/blockscout/pull/1936), [#1925](https://github.com/blockscout/blockscout/pull/1925), [#1922](https://github.com/blockscout/blockscout/pull/1922), [#1903](https://github.com/blockscout/blockscout/pull/1903), [#1874](https://github.com/blockscout/blockscout/pull/1874), [#1895](https://github.com/blockscout/blockscout/pull/1895), [#2031](https://github.com/blockscout/blockscout/pull/2031), [#2073](https://github.com/blockscout/blockscout/pull/2073), [#2074](https://github.com/blockscout/blockscout/pull/2074), - added new themes and logos for poa, eth, rinkeby, goerli, ropsten, kovan, sokol, xdai, etc, rsk and default theme @@ -1395,6 +1845,7 @@ fixed menu hovers in dark mode desktop view - [#2100](https://github.com/blockscout/blockscout/pull/2100) - feat: eth_get_balance rpc endpoint ### Fixes + - [#2228](https://github.com/blockscout/blockscout/pull/2228) - favorites duplication issues, active radio issue - [#2207](https://github.com/blockscout/blockscout/pull/2207) - new 'download csv' button design - [#2206](https://github.com/blockscout/blockscout/pull/2206) - added styles for 'Download All Transactions as CSV' button @@ -1448,7 +1899,6 @@ fixed menu hovers in dark mode desktop view - [#2055](https://github.com/blockscout/blockscout/pull/2055) - Increase timeout for geth indexers - [#2069](https://github.com/blockscout/blockscout/pull/2069) - Docsify integration: static docs page generation - ## 1.3.15-beta ### Features @@ -1463,7 +1913,6 @@ fixed menu hovers in dark mode desktop view - [#1992](https://github.com/blockscout/blockscout/pull/1992) - fix: support https for wobserver polling - [#2027](https://github.com/blockscout/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions - ## 1.3.14-beta - [#1812](https://github.com/blockscout/blockscout/pull/1812) - add pagination to addresses page @@ -1478,7 +1927,6 @@ fixed menu hovers in dark mode desktop view - [#1892](https://github.com/blockscout/blockscout/pull/1892) - Remove temporary worker modules - ## 1.3.13-beta ### Features @@ -1491,12 +1939,10 @@ fixed menu hovers in dark mode desktop view - [#1881](https://github.com/blockscout/blockscout/pull/1881) - fix: store solc versions locally for performance - [#1898](https://github.com/blockscout/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments - ## 1.3.12-beta Reverting of synchronous block counter, implemented in #1848 - ## 1.3.11-beta ### Features @@ -1518,7 +1964,6 @@ Reverting of synchronous block counter, implemented in #1848 - [#1814](https://github.com/blockscout/blockscout/pull/1814) - Clear build artefacts script - [#1837](https://github.com/blockscout/blockscout/pull/1837) - Add -f flag to clear_build.sh script delete static folder - ## 1.3.10-beta ### Features @@ -1533,165 +1978,162 @@ Reverting of synchronous block counter, implemented in #1848 ### Fixes - - [#1724](https://github.com/blockscout/blockscout/pull/1724) - Remove internal tx and token balance fetching from realtime fetcher - - [#1727](https://github.com/blockscout/blockscout/pull/1727) - add logs pagination in rpc api - - [#1740](https://github.com/blockscout/blockscout/pull/1740) - fix empty block time - - [#1743](https://github.com/blockscout/blockscout/pull/1743) - sort decompiled smart contracts in lexicographical order - - [#1756](https://github.com/blockscout/blockscout/pull/1756) - add today's token balance from the previous value - - [#1769](https://github.com/blockscout/blockscout/pull/1769) - add timestamp to block overview - - [#1768](https://github.com/blockscout/blockscout/pull/1768) - fix first block parameter - - [#1778](https://github.com/blockscout/blockscout/pull/1778) - Make websocket optional for realtime fetcher - - [#1790](https://github.com/blockscout/blockscout/pull/1790) - fix constructor arguments verification - - [#1793](https://github.com/blockscout/blockscout/pull/1793) - fix top nav autocomplete - - [#1795](https://github.com/blockscout/blockscout/pull/1795) - fix line numbers for decompiled contracts - - [#1803](https://github.com/blockscout/blockscout/pull/1803) - use coinmarketcap for total_supply by default - - [#1802](https://github.com/blockscout/blockscout/pull/1802) - make coinmarketcap's number of pages configurable - - [#1799](https://github.com/blockscout/blockscout/pull/1799) - Use eth_getUncleByBlockHashAndIndex for uncle block fetching - - [#1531](https://github.com/blockscout/blockscout/pull/1531) - docker: fix dockerFile for secp256k1 building - - [#1835](https://github.com/blockscout/blockscout/pull/1835) - fix: ignore `pong` messages without error +- [#1724](https://github.com/blockscout/blockscout/pull/1724) - Remove internal tx and token balance fetching from realtime fetcher +- [#1727](https://github.com/blockscout/blockscout/pull/1727) - add logs pagination in rpc api +- [#1740](https://github.com/blockscout/blockscout/pull/1740) - fix empty block time +- [#1743](https://github.com/blockscout/blockscout/pull/1743) - sort decompiled smart contracts in lexicographical order +- [#1756](https://github.com/blockscout/blockscout/pull/1756) - add today's token balance from the previous value +- [#1769](https://github.com/blockscout/blockscout/pull/1769) - add timestamp to block overview +- [#1768](https://github.com/blockscout/blockscout/pull/1768) - fix first block parameter +- [#1778](https://github.com/blockscout/blockscout/pull/1778) - Make websocket optional for realtime fetcher +- [#1790](https://github.com/blockscout/blockscout/pull/1790) - fix constructor arguments verification +- [#1793](https://github.com/blockscout/blockscout/pull/1793) - fix top nav autocomplete +- [#1795](https://github.com/blockscout/blockscout/pull/1795) - fix line numbers for decompiled contracts +- [#1803](https://github.com/blockscout/blockscout/pull/1803) - use coinmarketcap for total_supply by default +- [#1802](https://github.com/blockscout/blockscout/pull/1802) - make coinmarketcap's number of pages configurable +- [#1799](https://github.com/blockscout/blockscout/pull/1799) - Use eth_getUncleByBlockHashAndIndex for uncle block fetching +- [#1531](https://github.com/blockscout/blockscout/pull/1531) - docker: fix dockerFile for secp256k1 building +- [#1835](https://github.com/blockscout/blockscout/pull/1835) - fix: ignore `pong` messages without error ### Chore - - [#1804](https://github.com/blockscout/blockscout/pull/1804) - (Chore) Divide chains by Mainnet/Testnet in menu - - [#1783](https://github.com/blockscout/blockscout/pull/1783) - Update README with the chains that use Blockscout - - [#1780](https://github.com/blockscout/blockscout/pull/1780) - Update link to the Github repo in the footer - - [#1757](https://github.com/blockscout/blockscout/pull/1757) - Change twitter acc link to official Blockscout acc twitter - - [#1749](https://github.com/blockscout/blockscout/pull/1749) - Replace the link in the footer with the official POA announcements tg channel link - - [#1718](https://github.com/blockscout/blockscout/pull/1718) - Flatten indexer module hierarchy and supervisor tree - - [#1753](https://github.com/blockscout/blockscout/pull/1753) - Add a check mark to decompiled contract tab - - [#1744](https://github.com/blockscout/blockscout/pull/1744) - remove `0x0..0` from tests - - [#1763](https://github.com/blockscout/blockscout/pull/1763) - Describe indexer structure and list existing fetchers - - [#1800](https://github.com/blockscout/blockscout/pull/1800) - Disable lazy logging check in Credo - +- [#1804](https://github.com/blockscout/blockscout/pull/1804) - (Chore) Divide chains by Mainnet/Testnet in menu +- [#1783](https://github.com/blockscout/blockscout/pull/1783) - Update README with the chains that use Blockscout +- [#1780](https://github.com/blockscout/blockscout/pull/1780) - Update link to the Github repo in the footer +- [#1757](https://github.com/blockscout/blockscout/pull/1757) - Change twitter acc link to official Blockscout acc twitter +- [#1749](https://github.com/blockscout/blockscout/pull/1749) - Replace the link in the footer with the official POA announcements tg channel link +- [#1718](https://github.com/blockscout/blockscout/pull/1718) - Flatten indexer module hierarchy and supervisor tree +- [#1753](https://github.com/blockscout/blockscout/pull/1753) - Add a check mark to decompiled contract tab +- [#1744](https://github.com/blockscout/blockscout/pull/1744) - remove `0x0..0` from tests +- [#1763](https://github.com/blockscout/blockscout/pull/1763) - Describe indexer structure and list existing fetchers +- [#1800](https://github.com/blockscout/blockscout/pull/1800) - Disable lazy logging check in Credo ## 1.3.9-beta ### Features - - [#1662](https://github.com/blockscout/blockscout/pull/1662) - allow specifying number of optimization runs - - [#1654](https://github.com/blockscout/blockscout/pull/1654) - add decompiled code tab - - [#1661](https://github.com/blockscout/blockscout/pull/1661) - try to compile smart contract with the latest evm version - - [#1665](https://github.com/blockscout/blockscout/pull/1665) - Add contract verification RPC endpoint. - - [#1706](https://github.com/blockscout/blockscout/pull/1706) - allow setting update interval for addresses with b +- [#1662](https://github.com/blockscout/blockscout/pull/1662) - allow specifying number of optimization runs +- [#1654](https://github.com/blockscout/blockscout/pull/1654) - add decompiled code tab +- [#1661](https://github.com/blockscout/blockscout/pull/1661) - try to compile smart contract with the latest evm version +- [#1665](https://github.com/blockscout/blockscout/pull/1665) - Add contract verification RPC endpoint. +- [#1706](https://github.com/blockscout/blockscout/pull/1706) - allow setting update interval for addresses with b ### Fixes - - [#1669](https://github.com/blockscout/blockscout/pull/1669) - do not fail if multiple matching tokens are found - - [#1691](https://github.com/blockscout/blockscout/pull/1691) - decrease token metadata update interval - - [#1688](https://github.com/blockscout/blockscout/pull/1688) - do not fail if failure reason is atom - - [#1692](https://github.com/blockscout/blockscout/pull/1692) - exclude decompiled smart contract from encoding - - [#1684](https://github.com/blockscout/blockscout/pull/1684) - Discard child block with parent_hash not matching hash of imported block - - [#1699](https://github.com/blockscout/blockscout/pull/1699) - use seconds as transaction cache period measure - - [#1697](https://github.com/blockscout/blockscout/pull/1697) - fix failing in rpc if balance is empty - - [#1711](https://github.com/blockscout/blockscout/pull/1711) - rescue failing repo in block number cache update - - [#1712](https://github.com/blockscout/blockscout/pull/1712) - do not set contract code from transaction input - - [#1714](https://github.com/blockscout/blockscout/pull/1714) - fix average block time calculation +- [#1669](https://github.com/blockscout/blockscout/pull/1669) - do not fail if multiple matching tokens are found +- [#1691](https://github.com/blockscout/blockscout/pull/1691) - decrease token metadata update interval +- [#1688](https://github.com/blockscout/blockscout/pull/1688) - do not fail if failure reason is atom +- [#1692](https://github.com/blockscout/blockscout/pull/1692) - exclude decompiled smart contract from encoding +- [#1684](https://github.com/blockscout/blockscout/pull/1684) - Discard child block with parent_hash not matching hash of imported block +- [#1699](https://github.com/blockscout/blockscout/pull/1699) - use seconds as transaction cache period measure +- [#1697](https://github.com/blockscout/blockscout/pull/1697) - fix failing in rpc if balance is empty +- [#1711](https://github.com/blockscout/blockscout/pull/1711) - rescue failing repo in block number cache update +- [#1712](https://github.com/blockscout/blockscout/pull/1712) - do not set contract code from transaction input +- [#1714](https://github.com/blockscout/blockscout/pull/1714) - fix average block time calculation ### Chore - - [#1693](https://github.com/blockscout/blockscout/pull/1693) - Add a checklist to the PR template - +- [#1693](https://github.com/blockscout/blockscout/pull/1693) - Add a checklist to the PR template ## 1.3.8-beta ### Features - - [#1611](https://github.com/blockscout/blockscout/pull/1611) - allow setting the first indexing block - - [#1596](https://github.com/blockscout/blockscout/pull/1596) - add endpoint to create decompiled contracts - - [#1634](https://github.com/blockscout/blockscout/pull/1634) - add transaction count cache +- [#1611](https://github.com/blockscout/blockscout/pull/1611) - allow setting the first indexing block +- [#1596](https://github.com/blockscout/blockscout/pull/1596) - add endpoint to create decompiled contracts +- [#1634](https://github.com/blockscout/blockscout/pull/1634) - add transaction count cache ### Fixes - - [#1630](https://github.com/blockscout/blockscout/pull/1630) - (Fix) colour for release link in the footer - - [#1621](https://github.com/blockscout/blockscout/pull/1621) - Modify query to fetch failed contract creations - - [#1614](https://github.com/blockscout/blockscout/pull/1614) - Do not fetch burn address token balance - - [#1639](https://github.com/blockscout/blockscout/pull/1614) - Optimize token holder count updates when importing address current balances - - [#1643](https://github.com/blockscout/blockscout/pull/1643) - Set internal_transactions_indexed_at for empty blocks - - [#1647](https://github.com/blockscout/blockscout/pull/1647) - Fix typo in view - - [#1650](https://github.com/blockscout/blockscout/pull/1650) - Add petersburg evm version to smart contract verifier - - [#1657](https://github.com/blockscout/blockscout/pull/1657) - Force consensus loss for parent block if its hash mismatches parent_hash +- [#1630](https://github.com/blockscout/blockscout/pull/1630) - (Fix) colour for release link in the footer +- [#1621](https://github.com/blockscout/blockscout/pull/1621) - Modify query to fetch failed contract creations +- [#1614](https://github.com/blockscout/blockscout/pull/1614) - Do not fetch burn address token balance +- [#1639](https://github.com/blockscout/blockscout/pull/1614) - Optimize token holder count updates when importing address current balances +- [#1643](https://github.com/blockscout/blockscout/pull/1643) - Set internal_transactions_indexed_at for empty blocks +- [#1647](https://github.com/blockscout/blockscout/pull/1647) - Fix typo in view +- [#1650](https://github.com/blockscout/blockscout/pull/1650) - Add petersburg evm version to smart contract verifier +- [#1657](https://github.com/blockscout/blockscout/pull/1657) - Force consensus loss for parent block if its hash mismatches parent_hash ### Chore - ## 1.3.7-beta ### Features ### Fixes - - [#1615](https://github.com/blockscout/blockscout/pull/1615) - Add more logging to code fixer process - - [#1613](https://github.com/blockscout/blockscout/pull/1613) - Fix USD fee value - - [#1577](https://github.com/blockscout/blockscout/pull/1577) - Add process to fix contract with code - - [#1583](https://github.com/blockscout/blockscout/pull/1583) - Chunk JSON-RPC batches in case connection times out +- [#1615](https://github.com/blockscout/blockscout/pull/1615) - Add more logging to code fixer process +- [#1613](https://github.com/blockscout/blockscout/pull/1613) - Fix USD fee value +- [#1577](https://github.com/blockscout/blockscout/pull/1577) - Add process to fix contract with code +- [#1583](https://github.com/blockscout/blockscout/pull/1583) - Chunk JSON-RPC batches in case connection times out ### Chore - - [#1610](https://github.com/blockscout/blockscout/pull/1610) - Add PIRL to Readme - +- [#1610](https://github.com/blockscout/blockscout/pull/1610) - Add PIRL to Readme ## 1.3.6-beta ### Features - - [#1589](https://github.com/blockscout/blockscout/pull/1589) - RPC endpoint to list addresses - - [#1567](https://github.com/blockscout/blockscout/pull/1567) - Allow setting different configuration just for realtime fetcher - - [#1562](https://github.com/blockscout/blockscout/pull/1562) - Add incoming transactions count to contract view - - [#1608](https://github.com/blockscout/blockscout/pull/1608) - Add listcontracts RPC Endpoint +- [#1589](https://github.com/blockscout/blockscout/pull/1589) - RPC endpoint to list addresses +- [#1567](https://github.com/blockscout/blockscout/pull/1567) - Allow setting different configuration just for realtime fetcher +- [#1562](https://github.com/blockscout/blockscout/pull/1562) - Add incoming transactions count to contract view +- [#1608](https://github.com/blockscout/blockscout/pull/1608) - Add listcontracts RPC Endpoint ### Fixes - - [#1595](https://github.com/blockscout/blockscout/pull/1595) - Reduce block_rewards in the catchup fetcher - - [#1590](https://github.com/blockscout/blockscout/pull/1590) - Added guard for fetching blocks with invalid number - - [#1588](https://github.com/blockscout/blockscout/pull/1588) - Fix usd value on address page - - [#1586](https://github.com/blockscout/blockscout/pull/1586) - Exact timestamp display - - [#1581](https://github.com/blockscout/blockscout/pull/1581) - Consider `creates` param when fetching transactions - - [#1559](https://github.com/blockscout/blockscout/pull/1559) - Change v column type for Transactions table +- [#1595](https://github.com/blockscout/blockscout/pull/1595) - Reduce block_rewards in the catchup fetcher +- [#1590](https://github.com/blockscout/blockscout/pull/1590) - Added guard for fetching blocks with invalid number +- [#1588](https://github.com/blockscout/blockscout/pull/1588) - Fix usd value on address page +- [#1586](https://github.com/blockscout/blockscout/pull/1586) - Exact timestamp display +- [#1581](https://github.com/blockscout/blockscout/pull/1581) - Consider `creates` param when fetching transactions +- [#1559](https://github.com/blockscout/blockscout/pull/1559) - Change v column type for Transactions table ### Chore - - [#1579](https://github.com/blockscout/blockscout/pull/1579) - Add SpringChain to the list of Additional Chains Utilizing BlockScout - - [#1578](https://github.com/blockscout/blockscout/pull/1578) - Refine contributing procedure - - [#1572](https://github.com/blockscout/blockscout/pull/1572) - Add option to disable block rewards in indexer config - +- [#1579](https://github.com/blockscout/blockscout/pull/1579) - Add SpringChain to the list of Additional Chains Utilizing BlockScout +- [#1578](https://github.com/blockscout/blockscout/pull/1578) - Refine contributing procedure +- [#1572](https://github.com/blockscout/blockscout/pull/1572) - Add option to disable block rewards in indexer config ## 1.3.5-beta ### Features - - [#1560](https://github.com/blockscout/blockscout/pull/1560) - Allow executing smart contract functions in arbitrarily sized batches - - [#1543](https://github.com/blockscout/blockscout/pull/1543) - Use trace_replayBlockTransactions API for faster tracing - - [#1558](https://github.com/blockscout/blockscout/pull/1558) - Allow searching by token symbol - - [#1551](https://github.com/blockscout/blockscout/pull/1551) Exact date and time for Transaction details page - - [#1547](https://github.com/blockscout/blockscout/pull/1547) - Verify smart contracts with evm versions - - [#1540](https://github.com/blockscout/blockscout/pull/1540) - Fetch ERC721 token balances if sender is '0x0..0' - - [#1539](https://github.com/blockscout/blockscout/pull/1539) - Add the link to release in the footer - - [#1519](https://github.com/blockscout/blockscout/pull/1519) - Create contract methods - - [#1496](https://github.com/blockscout/blockscout/pull/1496) - Remove dropped/replaced transactions in pending transactions list - - [#1492](https://github.com/blockscout/blockscout/pull/1492) - Disable usd value for an empty exchange rate - - [#1466](https://github.com/blockscout/blockscout/pull/1466) - Decoding candidates for unverified contracts +- [#1560](https://github.com/blockscout/blockscout/pull/1560) - Allow executing smart contract functions in arbitrarily sized batches +- [#1543](https://github.com/blockscout/blockscout/pull/1543) - Use trace_replayBlockTransactions API for faster tracing +- [#1558](https://github.com/blockscout/blockscout/pull/1558) - Allow searching by token symbol +- [#1551](https://github.com/blockscout/blockscout/pull/1551) Exact date and time for Transaction details page +- [#1547](https://github.com/blockscout/blockscout/pull/1547) - Verify smart contracts with evm versions +- [#1540](https://github.com/blockscout/blockscout/pull/1540) - Fetch ERC721 token balances if sender is '0x0..0' +- [#1539](https://github.com/blockscout/blockscout/pull/1539) - Add the link to release in the footer +- [#1519](https://github.com/blockscout/blockscout/pull/1519) - Create contract methods +- [#1496](https://github.com/blockscout/blockscout/pull/1496) - Remove dropped/replaced transactions in pending transactions list +- [#1492](https://github.com/blockscout/blockscout/pull/1492) - Disable usd value for an empty exchange rate +- [#1466](https://github.com/blockscout/blockscout/pull/1466) - Decoding candidates for unverified contracts ### Fixes - - [#1545](https://github.com/blockscout/blockscout/pull/1545) - Fix scheduling of latest block polling in Realtime Fetcher - - [#1554](https://github.com/blockscout/blockscout/pull/1554) - Encode integer parameters when calling smart contract functions - - [#1537](https://github.com/blockscout/blockscout/pull/1537) - Fix test that depended on date - - [#1534](https://github.com/blockscout/blockscout/pull/1534) - Render a nicer error when creator cannot be determined - - [#1527](https://github.com/blockscout/blockscout/pull/1527) - Add index to value_fetched_at - - [#1518](https://github.com/blockscout/blockscout/pull/1518) - Select only distinct failed transactions - - [#1516](https://github.com/blockscout/blockscout/pull/1516) - Fix coin balance params reducer for pending transaction - - [#1511](https://github.com/blockscout/blockscout/pull/1511) - Set correct log level for production - - [#1510](https://github.com/blockscout/blockscout/pull/1510) - Fix test that fails every 1st day of the month - - [#1509](https://github.com/blockscout/blockscout/pull/1509) - Add index to blocks' consensus - - [#1508](https://github.com/blockscout/blockscout/pull/1508) - Remove duplicated indexes - - [#1505](https://github.com/blockscout/blockscout/pull/1505) - Use https instead of ssh for absinthe libs - - [#1501](https://github.com/blockscout/blockscout/pull/1501) - Constructor_arguments must be type `text` - - [#1498](https://github.com/blockscout/blockscout/pull/1498) - Add index for created_contract_address_hash in transactions - - [#1493](https://github.com/blockscout/blockscout/pull/1493) - Do not do work in process initialization - - [#1487](https://github.com/blockscout/blockscout/pull/1487) - Limit geth sync to 128 blocks - - [#1484](https://github.com/blockscout/blockscout/pull/1484) - Allow decoding input as utf-8 - - [#1479](https://github.com/blockscout/blockscout/pull/1479) - Remove smoothing from coin balance chart + +- [#1545](https://github.com/blockscout/blockscout/pull/1545) - Fix scheduling of latest block polling in Realtime Fetcher +- [#1554](https://github.com/blockscout/blockscout/pull/1554) - Encode integer parameters when calling smart contract functions +- [#1537](https://github.com/blockscout/blockscout/pull/1537) - Fix test that depended on date +- [#1534](https://github.com/blockscout/blockscout/pull/1534) - Render a nicer error when creator cannot be determined +- [#1527](https://github.com/blockscout/blockscout/pull/1527) - Add index to value_fetched_at +- [#1518](https://github.com/blockscout/blockscout/pull/1518) - Select only distinct failed transactions +- [#1516](https://github.com/blockscout/blockscout/pull/1516) - Fix coin balance params reducer for pending transaction +- [#1511](https://github.com/blockscout/blockscout/pull/1511) - Set correct log level for production +- [#1510](https://github.com/blockscout/blockscout/pull/1510) - Fix test that fails every 1st day of the month +- [#1509](https://github.com/blockscout/blockscout/pull/1509) - Add index to blocks' consensus +- [#1508](https://github.com/blockscout/blockscout/pull/1508) - Remove duplicated indexes +- [#1505](https://github.com/blockscout/blockscout/pull/1505) - Use https instead of ssh for absinthe libs +- [#1501](https://github.com/blockscout/blockscout/pull/1501) - Constructor_arguments must be type `text` +- [#1498](https://github.com/blockscout/blockscout/pull/1498) - Add index for created_contract_address_hash in transactions +- [#1493](https://github.com/blockscout/blockscout/pull/1493) - Do not do work in process initialization +- [#1487](https://github.com/blockscout/blockscout/pull/1487) - Limit geth sync to 128 blocks +- [#1484](https://github.com/blockscout/blockscout/pull/1484) - Allow decoding input as utf-8 +- [#1479](https://github.com/blockscout/blockscout/pull/1479) - Remove smoothing from coin balance chart ### Chore - - [https://github.com/blockscout/blockscout/pull/1532](https://github.com/blockscout/blockscout/pull/1532) - Upgrade elixir to 1.8.1 - - [https://github.com/blockscout/blockscout/pull/1553](https://github.com/blockscout/blockscout/pull/1553) - Dockerfile: remove 1.7.1 version pin FROM bitwalker/alpine-elixir-phoenix - - [https://github.com/blockscout/blockscout/pull/1465](https://github.com/blockscout/blockscout/pull/1465) - Resolve lodash security alert + +- [https://github.com/blockscout/blockscout/pull/1532](https://github.com/blockscout/blockscout/pull/1532) - Upgrade elixir to 1.8.1 +- [https://github.com/blockscout/blockscout/pull/1553](https://github.com/blockscout/blockscout/pull/1553) - Dockerfile: remove 1.7.1 version pin FROM bitwalker/alpine-elixir-phoenix +- [https://github.com/blockscout/blockscout/pull/1465](https://github.com/blockscout/blockscout/pull/1465) - Resolve lodash security alert diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79125b999467..122503bc252f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## Contributing -1. Fork it ( https://github.com/blockscout/blockscout/fork ) +1. Fork it ( ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Write tests that cover your work 4. Commit your changes (`git commit -am 'Add some feature'`) @@ -21,6 +21,7 @@ ### Enhancements Enhancements cover all changes that make users lives better: + * [feature requests filed as issues](https://github.com/blockscout/blockscout/labels/enhancement) that impact end-user [contributors](https://github.com/blockscout/blockscout/labels/contributor) and [developers](https://github.com/blockscout/blockscout/labels/developer) * changes to the [architecture](https://github.com/blockscout/blockscout/labels/architecture) that make it easier for contributors (in the GitHub sense), dev-ops, and deployers to maintain and run blockscout @@ -39,6 +40,7 @@ Incompatible changes can arise as a side-effect of either Enhancements or Bug Fi * Incompatible changes should be called out explicitly, with any steps the various user roles need to do to upgrade. * If a schema change occurs that requires a re-index add the following to the Pull Request description: + ```markdown **NOTE**: A database reset and re-index is required ``` @@ -47,12 +49,12 @@ Incompatible changes can arise as a side-effect of either Enhancements or Bug Fi There is a [PULL_REQUEST_TEMPLATE.md](PULL_REQUEST_TEMPLATE.md) for this repository, but since it can't fill in the title for you, please follow the following steps when opening a Pull Request before filling in the template: -- [ ] Title - - [ ] Prefix labels if you don't have permissions to set labels in the GitHub interface. - * (bug) for [bug](https://github.com/blockscout/blockscout/labels/bug) fixes - * (enhancement) for [enhancement](https://github.com/blockscout/blockscout/labels/enhancement)s - * (incompatible changes) for [incompatible changes](https://github.com/blockscout/blockscout/labels/incompatible%20changes), such a refactor that removes functionality, changes arguments, or makes something required that wasn't previously. - - [ ] Single sentence summary of change +* [ ] Title + * [ ] Prefix labels if you don't have permissions to set labels in the GitHub interface. + * (bug) for [bug](https://github.com/blockscout/blockscout/labels/bug) fixes + * (enhancement) for [enhancement](https://github.com/blockscout/blockscout/labels/enhancement)s + * (incompatible changes) for [incompatible changes](https://github.com/blockscout/blockscout/labels/incompatible%20changes), such a refactor that removes functionality, changes arguments, or makes something required that wasn't previously. + * [ ] Single sentence summary of change * What was fixed for bugs * What was added for enhancements * What was changed for incompatible changes diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 3075397e8ddb..1e5bff013634 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -2,9 +2,12 @@ ### Environment +* Deployment type (Manual/Docker/Docker-compose): * Elixir & Erlang/OTP versions (`elixir -version`): +* Node JS version (`node -v`): * Operating System: -* Blockscout Version/branch: +* Blockscout Version/branch/commit: +* Archive node type && version (Erigon/Geth/Nethermind/Ganache/?): ### Steps to reproduce diff --git a/README.md b/README.md index cb6173ce67b5..316e101047f5 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,12 @@

Blockchain Explorer for inspecting and analyzing EVM Chains.

-[![Blockscout](https://github.com/blockscout/blockscout/workflows/Blockscout/badge.svg?branch=master)](https://github.com/blockscout/blockscout/actions) +[![Blockscout](https://github.com/blockscout/blockscout/workflows/Blockscout/badge.svg?branch=master)](https://github.com/blockscout/blockscout/actions) +[![](https://dcbadge.vercel.app/api/server/blockscout?style=flat)](https://discord.gg/blockscout)
+ BlockScout provides a comprehensive, easy-to-use interface for users to view, confirm, and inspect transactions on EVM (Ethereum Virtual Machine) blockchains. This includes the POA Network, Gnosis Chain, Ethereum Classic and other **Ethereum testnets, private networks and sidechains**. See our [project documentation](https://docs.blockscout.com/) for detailed information and setup instructions. @@ -20,22 +22,21 @@ Currently available full-featured block explorers (Etherscan, Etherchain, Blockc ## Supported Projects -BlockScout supports a number of projects. Hosted instances include POA Network, Gnosis Chain, Ethereum Classic, Sokol & Kovan testnets, and other EVM chains. +BlockScout supports a number of projects. Hosted instances include POA Network, Gnosis Chain, Ethereum Classic, Sokol & Kovan testnets, and other EVM chains. - [List of hosted mainnets, testnets, and additional chains using BlockScout](https://docs.blockscout.com/for-projects/supported-projects) - [Hosted instance versions](https://docs.blockscout.com/about/use-cases/hosted-blockscout) - ## Getting Started See the [project documentation](https://docs.blockscout.com/) for instructions: + - [Requirements](https://docs.blockscout.com/for-developers/information-and-settings/requirements) - [Ansible deployment](https://docs.blockscout.com/for-developers/ansible-deployment) - [Manual deployment](https://docs.blockscout.com/for-developers/manual-deployment) - [ENV variables](https://docs.blockscout.com/for-developers/information-and-settings/env-variables) - [Configuration options](https://docs.blockscout.com/for-developers/configuration-options) - ## Acknowledgements We would like to thank the [EthPrize foundation](http://ethprize.io/) for their funding support. diff --git a/apps/block_scout_web/API blueprint.md b/apps/block_scout_web/API blueprint.md new file mode 100644 index 000000000000..d473b194a930 --- /dev/null +++ b/apps/block_scout_web/API blueprint.md @@ -0,0 +1,3278 @@ +FORMAT: 1A +HOST:http://blockscout.com/poa/core +# + + +# API Documentation + + +# Group BlockScoutWeb.Account.Api.V1.UserController +## BlockScoutWeb.Account.Api.V1.UserController [/api/account/v1/user/info] +### BlockScoutWeb.Account.Api.V1.UserController info [GET /api/account/v1/user/info] + + + + + ++ Request Get info about user +**GET**  `/api/account/v1/user/info` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjNkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTM3QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABHGQABG5hbWVtAAAAC1VzZXIgVGVzdDIzZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIzZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIzZAAMd2F0Y2hsaXN0X2lkYgAAARw.E0Sm_2oS5AyE0tua4lSouZRAcWS_F5ZcfGxLWSTUkXA; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2a5ilyuHABAAABjC + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "nickname": "test_user23", + "name": "User Test23", + "email": "test_user-37@blockscout.com", + "avatar": "https://example.com/avatar/test_user23" + } +### BlockScoutWeb.Account.Api.V1.UserController create_tag_address [POST /api/account/v1/user/tags/address] + + + + + ++ Request Add private address tag +**POST**  `/api/account/v1/user/tags/address` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "MyName", + "address_hash": "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyN2QABWVtYWlsbQAAABt0ZXN0X3VzZXItMTdAYmxvY2tzY291dC5jb21kAAJpZGIAAAEMZAAEbmFtZW0AAAAKVXNlciBUZXN0N2QACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI3ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDdkAAx3YXRjaGxpc3RfaWRiAAABDA.nTbrGL1cYPUoZ-N2MiHq9YBaqutQsS6G_gJBJmjD_mE; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2Za89gG9wigAABTB + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "MyName", + "id": 66, + "address_hash": "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b", + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x3E9AC8f16C92bc4F093357933B5BEFBF1E16987B" + } + } + +# Group BlockScoutWeb.Account.Api.V1.TagsController +## BlockScoutWeb.Account.Api.V1.TagsController [/api/account/v1/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b] +### BlockScoutWeb.Account.Api.V1.TagsController tags_address [GET /api/account/v1/tags/address/{address_hash}] + + + + ++ Parameters + + address_hash: `0x3e9ac8f16c92bc4f093357933b5befbf1e16987b` + address_hash: 0x3e9ac8f16c92bc4f093357933b5befbf1e16987b + + ++ Request Get tags for address +**GET**  `/api/account/v1/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyN2QABWVtYWlsbQAAABt0ZXN0X3VzZXItMTdAYmxvY2tzY291dC5jb21kAAJpZGIAAAEMZAAEbmFtZW0AAAAKVXNlciBUZXN0N2QACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI3ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDdkAAx3YXRjaGxpc3RfaWRiAAABDA.nTbrGL1cYPUoZ-N2MiHq9YBaqutQsS6G_gJBJmjD_mE; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2ZcSwwK9wigAABMC + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "watchlist_names": [], + "personal_tags": [ + { + "label": "MyName", + "display_name": "MyName", + "address_hash": "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b" + } + ], + "common_tags": [] + } + +# Group BlockScoutWeb.Account.Api.V1.UserController +## BlockScoutWeb.Account.Api.V1.UserController [/api/account/v1/user/tags/address/70] +### BlockScoutWeb.Account.Api.V1.UserController update_tag_address [PUT /api/account/v1/user/tags/address/{id}] + + + + ++ Parameters + + id: `70` + id: 70 + + ++ Request Edit private address tag +**PUT**  `/api/account/v1/user/tags/address/70` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "name3", + "address_hash": "0x000000000000000000000000000000000000007e" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTlkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTMxQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABGGQABG5hbWVtAAAAC1VzZXIgVGVzdDE5ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE5ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE5ZAAMd2F0Y2hsaXN0X2lkYgAAARg.gpllu6S6EuYQy2GBhhmdrwjWa7uNmRUMz8aoKGDaPQU; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aYSywZD3jIAAAQF + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "name3", + "id": 70, + "address_hash": "0x000000000000000000000000000000000000007e", + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000007E" + } + } +### BlockScoutWeb.Account.Api.V1.UserController tags_address [GET /api/account/v1/user/tags/address] + + + + + ++ Request Get private addresses tags +**GET**  `/api/account/v1/user/tags/address` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMThkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTMwQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABF2QABG5hbWVtAAAAC1VzZXIgVGVzdDE4ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE4ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE4ZAAMd2F0Y2hsaXN0X2lkYgAAARc.MgpnF7n_gJEhkWphCunY7unXVQWz6NAKdXJtAlCtm-E; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aT84qhvvqoAABfh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "name": "name2", + "id": 69, + "address_hash": "0x000000000000000000000000000000000000007c", + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000007c" + } + }, + { + "name": "name1", + "id": 68, + "address_hash": "0x000000000000000000000000000000000000007b", + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000007B" + } + }, + { + "name": "name0", + "id": 67, + "address_hash": "0x000000000000000000000000000000000000007a", + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000007a" + } + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_tag_address [DELETE /api/account/v1/user/tags/address/{id}] + + + + ++ Parameters + + id: `63` + id: 63 + + ++ Request Delete private address tag +**DELETE**  `/api/account/v1/user/tags/address/63` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNGQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTRAYmxvY2tzY291dC5jb21kAAJpZGIAAAEJZAAEbmFtZW0AAAAKVXNlciBUZXN0NGQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI0ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDRkAAx3YXRjaGxpc3RfaWRiAAABCQ.3f3SFCRJgY59jb-YfVwAjM-xZEMv78Z1X-yNR03pCOI; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2YwZcxcJlUgAABJh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "message": "OK" + } +### BlockScoutWeb.Account.Api.V1.UserController create_tag_transaction [POST /api/account/v1/user/tags/transaction] + + + + + ++ Request Create private transaction tag +**POST**  `/api/account/v1/user/tags/transaction` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000006", + "name": "MyName" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTVkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI3QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABFGQABG5hbWVtAAAAC1VzZXIgVGVzdDE1ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE1ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE1ZAAMd2F0Y2hsaXN0X2lkYgAAARQ.y7cpDUrwXiGxhgdOS0V14Rsohk8wJHkv940fW0Mw1YQ; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aDXRGevcEwAABYh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000006", + "name": "MyName", + "id": 61 + } + + ++ Request Error on try to create private transaction tag for tx does not exist +**POST**  `/api/account/v1/user/tags/transaction` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000005", + "name": "MyName" + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTVkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI3QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABFGQABG5hbWVtAAAAC1VzZXIgVGVzdDE1ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE1ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE1ZAAMd2F0Y2hsaXN0X2lkYgAAARQ.y7cpDUrwXiGxhgdOS0V14Rsohk8wJHkv940fW0Mw1YQ; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aCGof2vcEwAAAlk + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "errors": { + "tx_hash": [ + "Transaction does not exist" + ] + } + } + +# Group BlockScoutWeb.Account.Api.V1.TagsController +## BlockScoutWeb.Account.Api.V1.TagsController [/api/account/v1/tags/transaction/0x0000000000000000000000000000000000000000000000000000000000000006] +### BlockScoutWeb.Account.Api.V1.TagsController tags_transaction [GET /api/account/v1/tags/transaction/{transaction_hash}] + + + + ++ Parameters + + transaction_hash: `0x0000000000000000000000000000000000000000000000000000000000000006` + transaction_hash: 0x0000000000000000000000000000000000000000000000000000000000000006 + + ++ Request Get tags for transaction +**GET**  `/api/account/v1/tags/transaction/0x0000000000000000000000000000000000000000000000000000000000000006` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTVkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI3QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABFGQABG5hbWVtAAAAC1VzZXIgVGVzdDE1ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE1ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE1ZAAMd2F0Y2hsaXN0X2lkYgAAARQ.y7cpDUrwXiGxhgdOS0V14Rsohk8wJHkv940fW0Mw1YQ; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aEbojKvcEwAABZB + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "watchlist_names": [], + "personal_tx_tag": { + "label": "MyName" + }, + "personal_tags": [], + "common_tags": [] + } + +# Group BlockScoutWeb.Account.Api.V1.UserController +## BlockScoutWeb.Account.Api.V1.UserController [/api/account/v1/user/tags/transaction/57] +### BlockScoutWeb.Account.Api.V1.UserController update_tag_transaction [PUT /api/account/v1/user/tags/transaction/{id}] + + + + ++ Parameters + + id: `57` + id: 57 + + ++ Request Edit private transaction tag +**PUT**  `/api/account/v1/user/tags/transaction/57` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "name": "name1" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMWQABWVtYWlsbQAAABp0ZXN0X3VzZXItMUBibG9ja3Njb3V0LmNvbWQAAmlkYgAAAQZkAARuYW1lbQAAAApVc2VyIFRlc3QxZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjFkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMWQADHdhdGNobGlzdF9pZGIAAAEG.K4xvLgb-ji7_yiP-B80J_ItCchTMzzYcgcN7ku9a4B8; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2XSUU7NY8y8AAAME + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "name": "name1", + "id": 57 + } +### BlockScoutWeb.Account.Api.V1.UserController tags_transaction [GET /api/account/v1/user/tags/transaction] + + + + + ++ Request Get private transactions tags +**GET**  `/api/account/v1/user/tags/transaction` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTM2QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABG2QABG5hbWVtAAAAC1VzZXIgVGVzdDIyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIyZAAMd2F0Y2hsaXN0X2lkYgAAARs.O7Ha2Ze8DT1d2yaZbQEy9tZXE6OUDWyuh3yoyB2WNAU; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2a4GFi44x6sAABii + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000009", + "name": "name2", + "id": 64 + }, + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000008", + "name": "name1", + "id": 63 + }, + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000007", + "name": "name0", + "id": 62 + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_tag_transaction [DELETE /api/account/v1/user/tags/transaction/{id}] + + + + ++ Parameters + + id: `58` + id: 58 + + ++ Request Delete private transaction tag +**DELETE**  `/api/account/v1/user/tags/transaction/58` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTRkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI2QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABE2QABG5hbWVtAAAAC1VzZXIgVGVzdDE0ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE0ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE0ZAAMd2F0Y2hsaXN0X2lkYgAAARM.XN0A5eUbCpZdpnhayHyU-YiQ4jm1-WjwYxvGD6JVCmg; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2Z9NDKXc1FcAABYC + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "message": "OK" + } +### BlockScoutWeb.Account.Api.V1.UserController create_watchlist [POST /api/account/v1/user/watchlist] + + + + + ++ Request Add address to watch list +**POST**  `/api/account/v1/user/watchlist` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "notification_settings": { + "native": { + "outcoming": true, + "incoming": true + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": false + }, + "name": "test26", + "address_hash": "0x000000000000000000000000000000000000007f" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjBkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTMyQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABGWQABG5hbWVtAAAAC1VzZXIgVGVzdDIwZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIwZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIwZAAMd2F0Y2hsaXN0X2lkYgAAARk.vaGEF62HMb-YGk5JNfvq8xH6YkGmQaEEa1gpNIUmjJM; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2acnBbQAq20AAARF + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "notification_settings": { + "native": { + "outcoming": true, + "incoming": true + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": false + }, + "name": "test26", + "id": 73, + "exchange_rate": null, + "address_hash": "0x000000000000000000000000000000000000007f", + "address_balance": null, + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000007f" + } + } +### BlockScoutWeb.Account.Api.V1.UserController watchlist [GET /api/account/v1/user/watchlist] + + + + + ++ Request Get addresses from watchlists +**GET**  `/api/account/v1/user/watchlist` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjBkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTMyQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABGWQABG5hbWVtAAAAC1VzZXIgVGVzdDIwZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIwZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIwZAAMd2F0Y2hsaXN0X2lkYgAAARk.vaGEF62HMb-YGk5JNfvq8xH6YkGmQaEEa1gpNIUmjJM; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aiKtdsAq20AABhh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "notification_settings": { + "native": { + "outcoming": true, + "incoming": true + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": false, + "incoming": false + } + }, + "notification_methods": { + "email": true + }, + "name": "test27", + "id": 74, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000080", + "address_balance": null, + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000080" + } + }, + { + "notification_settings": { + "native": { + "outcoming": true, + "incoming": true + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": false + }, + "name": "test26", + "id": 73, + "exchange_rate": null, + "address_hash": "0x000000000000000000000000000000000000007f", + "address_balance": null, + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000007f" + } + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_watchlist [DELETE /api/account/v1/user/watchlist/{id}] + + + + ++ Parameters + + id: `72` + id: 72 + + ++ Request Delete address from watchlist by id +**DELETE**  `/api/account/v1/user/watchlist/72` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTdkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI5QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABFmQABG5hbWVtAAAAC1VzZXIgVGVzdDE3ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE3ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE3ZAAMd2F0Y2hsaXN0X2lkYgAAARY.bngpdS3ELd9RFd1465ZhfhaitqcUi6xG4s0BoDGWoAw; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aNXuJ9GNz0AABch + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "message": "OK" + } +### BlockScoutWeb.Account.Api.V1.UserController update_watchlist [PUT /api/account/v1/user/watchlist/{id}] + + + + ++ Parameters + + id: `70` + id: 70 + + ++ Request Edit watchlist address +**PUT**  `/api/account/v1/user/watchlist/70` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": false, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": true + } + }, + "notification_methods": { + "email": true + }, + "name": "test21", + "address_hash": "0x0000000000000000000000000000000000000064" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTBkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIxQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABD2QABG5hbWVtAAAAC1VzZXIgVGVzdDEwZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEwZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEwZAAMd2F0Y2hsaXN0X2lkYgAAAQ8.JqlZQRGTvi6UZy4cEjJW6UYnZgNo0LaoO3R4mxO_fFA; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2Zo1KOm2BRoAAAJl + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": false, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": true + } + }, + "notification_methods": { + "email": true + }, + "name": "test21", + "id": 70, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000064", + "address_balance": null, + "address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000064" + } + } +### BlockScoutWeb.Account.Api.V1.UserController create_watchlist [POST /api/account/v1/user/watchlist] + + + + + ++ Request Example of error on creating watchlist address +**POST**  `/api/account/v1/user/watchlist` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": false, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": true + } + }, + "notification_methods": { + "email": true + }, + "name": "test0", + "address_hash": "0x0000000000000000000000000000000000000001" + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMGQABWVtYWlsbQAAABp0ZXN0X3VzZXItMEBibG9ja3Njb3V0LmNvbWQAAmlkYgAAAQVkAARuYW1lbQAAAApVc2VyIFRlc3QwZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjBkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMGQADHdhdGNobGlzdF9pZGIAAAEF.4CS6L7Ror_vIdEgjt8Mh9y2TJagC83VObHAGZ-ABOI4; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2W1ZceoPnWQAAATj + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "errors": { + "watchlist_id": [ + "Address already added to the watch list" + ] + } + } +### BlockScoutWeb.Account.Api.V1.UserController update_watchlist [PUT /api/account/v1/user/watchlist/{id}] + + + + ++ Parameters + + id: `69` + id: 69 + + ++ Request Example of error on editing watchlist address +**PUT**  `/api/account/v1/user/watchlist/69` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": false, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": true + } + }, + "notification_methods": { + "email": true + }, + "name": "test0", + "address_hash": "0x0000000000000000000000000000000000000001" + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMGQABWVtYWlsbQAAABp0ZXN0X3VzZXItMEBibG9ja3Njb3V0LmNvbWQAAmlkYgAAAQVkAARuYW1lbQAAAApVc2VyIFRlc3QwZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjBkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMGQADHdhdGNobGlzdF9pZGIAAAEF.4CS6L7Ror_vIdEgjt8Mh9y2TJagC83VObHAGZ-ABOI4; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2W6esdoPnWQAAAKE + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "errors": { + "watchlist_id": [ + "Address already added to the watch list" + ] + } + } +### BlockScoutWeb.Account.Api.V1.UserController create_api_key [POST /api/account/v1/user/api_keys] + + + + + ++ Request Add api key +**POST**  `/api/account/v1/user/api_keys` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTZkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI4QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABFWQABG5hbWVtAAAAC1VzZXIgVGVzdDE2ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE2ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE2ZAAMd2F0Y2hsaXN0X2lkYgAAARU.bIr9Nod33f3ivryxZfzUGzSN34H8R1h_oOPJvRdulDY; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aGwztUoK_8AAAnk + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test", + "api_key": "5dcfeb7d-6a73-47ed-8001-130692ebdf30" + } + + ++ Request Example of error on creating api key +**POST**  `/api/account/v1/user/api_keys` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test" + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjRkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTM4QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABHWQABG5hbWVtAAAAC1VzZXIgVGVzdDI0ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjI0ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDI0ZAAMd2F0Y2hsaXN0X2lkYgAAAR0.K_0yxkRjZq43jcCKzlzgHFNjm7aB_BmvBzlTVbpDUYI; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2a-lcgwKyxIAAAuk + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "errors": { + "name": [ + "Max 3 keys per account" + ] + } + } +### BlockScoutWeb.Account.Api.V1.UserController api_keys [GET /api/account/v1/user/api_keys] + + + + + ++ Request Get api keys list +**GET**  `/api/account/v1/user/api_keys` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjRkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTM4QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABHWQABG5hbWVtAAAAC1VzZXIgVGVzdDI0ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjI0ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDI0ZAAMd2F0Y2hsaXN0X2lkYgAAAR0.K_0yxkRjZq43jcCKzlzgHFNjm7aB_BmvBzlTVbpDUYI; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2a-2qPMKyxIAABki + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "name": "test", + "api_key": "00c90b31-db68-4de5-8022-32b6d9bdfaf2" + }, + { + "name": "test", + "api_key": "936f1623-4cfb-4581-badf-ff82193cc55e" + }, + { + "name": "test", + "api_key": "8af19684-7d84-4fa5-bc5e-98391204fa21" + } + ] +### BlockScoutWeb.Account.Api.V1.UserController update_api_key [PUT /api/account/v1/user/api_keys/{api_key}] + + + + ++ Parameters + + api_key: `e6fcab8c-d092-415d-a64e-caeebdab7e0a` + api_key: e6fcab8c-d092-415d-a64e-caeebdab7e0a + + ++ Request Edit api key +**PUT**  `/api/account/v1/user/api_keys/e6fcab8c-d092-415d-a64e-caeebdab7e0a` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test_1" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTNkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI1QGJsb2Nrc2NvdXQuY29tZAACaWRiAAABEmQABG5hbWVtAAAAC1VzZXIgVGVzdDEzZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEzZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEzZAAMd2F0Y2hsaXN0X2lkYgAAARI.oCXF9HRta7QoX4kvCCJGwXim8h2PvKmQnL3qC-BrYT0; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2ZxOPw0OLVMAABTC + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test_1", + "api_key": "e6fcab8c-d092-415d-a64e-caeebdab7e0a" + } +### BlockScoutWeb.Account.Api.V1.UserController delete_api_key [DELETE /api/account/v1/user/api_keys/{api_key}] + + + + ++ Parameters + + api_key: `ed840181-ee0a-49e7-931c-ed12c44c3c5c` + api_key: ed840181-ee0a-49e7-931c-ed12c44c3c5c + + ++ Request Delete api key +**DELETE**  `/api/account/v1/user/api_keys/ed840181-ee0a-49e7-931c-ed12c44c3c5c` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyOGQABWVtYWlsbQAAABt0ZXN0X3VzZXItMThAYmxvY2tzY291dC5jb21kAAJpZGIAAAENZAAEbmFtZW0AAAAKVXNlciBUZXN0OGQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI4ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDhkAAx3YXRjaGxpc3RfaWRiAAABDQ.N8IAT9JlprYQcjF97-2AwyvKRZ2pWrOhPA-piu_yjxY; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2ZeeHae-W7UAABPi + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "message": "OK" + } +### BlockScoutWeb.Account.Api.V1.UserController create_custom_abi [POST /api/account/v1/user/custom_abis] + + + + + ++ Request Add custom abi +**POST**  `/api/account/v1/user/custom_abis` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test3", + "contract_address_hash": "0x0000000000000000000000000000000000000049", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNWQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTVAYmxvY2tzY291dC5jb21kAAJpZGIAAAEKZAAEbmFtZW0AAAAKVXNlciBUZXN0NWQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI1ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDVkAAx3YXRjaGxpc3RfaWRiAAABCg.Ed2YB-WoqETtu1WlAOdX7KJi6sFIJ1SGIeS89Aie2pg; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2Y2Ja_DGUGwAAAWE + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test3", + "id": 146, + "contract_address_hash": "0x0000000000000000000000000000000000000049", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000049" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + } + + ++ Request Example of error on creating custom abi +**POST**  `/api/account/v1/user/custom_abis` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test19", + "contract_address_hash": "0x0000000000000000000000000000000000000059", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNmQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTZAYmxvY2tzY291dC5jb21kAAJpZGIAAAELZAAEbmFtZW0AAAAKVXNlciBUZXN0NmQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI2ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDZkAAx3YXRjaGxpc3RfaWRiAAABCw.SNgNlsqLtHPQ2HgJTPlyNjbvKw2FlW_U6_cJXTD-ZE4; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2ZR-dhCywD0AABJC + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "errors": { + "name": [ + "Max 15 ABIs per account" + ] + } + } +### BlockScoutWeb.Account.Api.V1.UserController custom_abis [GET /api/account/v1/user/custom_abis] + + + + + ++ Request Get custom abis list +**GET**  `/api/account/v1/user/custom_abis` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNmQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTZAYmxvY2tzY291dC5jb21kAAJpZGIAAAELZAAEbmFtZW0AAAAKVXNlciBUZXN0NmQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI2ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDZkAAx3YXRjaGxpc3RfaWRiAAABCw.SNgNlsqLtHPQ2HgJTPlyNjbvKw2FlW_U6_cJXTD-ZE4; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2ZSytrGywD0AABJi + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "name": "test18", + "id": 161, + "contract_address_hash": "0x0000000000000000000000000000000000000058", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000058" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test17", + "id": 160, + "contract_address_hash": "0x0000000000000000000000000000000000000057", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000057" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test16", + "id": 159, + "contract_address_hash": "0x0000000000000000000000000000000000000056", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000056" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test15", + "id": 158, + "contract_address_hash": "0x0000000000000000000000000000000000000055", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000055" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test14", + "id": 157, + "contract_address_hash": "0x0000000000000000000000000000000000000054", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000054" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test13", + "id": 156, + "contract_address_hash": "0x0000000000000000000000000000000000000053", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000053" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test12", + "id": 155, + "contract_address_hash": "0x0000000000000000000000000000000000000052", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000052" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test11", + "id": 154, + "contract_address_hash": "0x0000000000000000000000000000000000000051", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000051" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test10", + "id": 153, + "contract_address_hash": "0x0000000000000000000000000000000000000050", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000050" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test9", + "id": 152, + "contract_address_hash": "0x000000000000000000000000000000000000004f", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000004f" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test8", + "id": 151, + "contract_address_hash": "0x000000000000000000000000000000000000004e", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000004e" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test7", + "id": 150, + "contract_address_hash": "0x000000000000000000000000000000000000004d", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000004D" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test6", + "id": 149, + "contract_address_hash": "0x000000000000000000000000000000000000004c", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000004C" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test5", + "id": 148, + "contract_address_hash": "0x000000000000000000000000000000000000004b", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000004B" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test4", + "id": 147, + "contract_address_hash": "0x000000000000000000000000000000000000004a", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000004A" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + } + ] +### BlockScoutWeb.Account.Api.V1.UserController update_custom_abi [PUT /api/account/v1/user/custom_abis/{id}] + + + + ++ Parameters + + id: `162` + id: 162 + + ++ Request Edit custom abi +**PUT**  `/api/account/v1/user/custom_abis/162` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test23", + "contract_address_hash": "0x0000000000000000000000000000000000000066", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIyQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABEGQABG5hbWVtAAAAC1VzZXIgVGVzdDExZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjExZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDExZAAMd2F0Y2hsaXN0X2lkYgAAARA.M0fGYF6uHlLOsjA-gLmGzzXuTxSr8hQVlDi3jIhAXX0; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2ZrqXJvdOdEAAAdE + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test23", + "id": 162, + "contract_address_hash": "0x0000000000000000000000000000000000000066", + "contract_address": { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": false, + "is_contract": true, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000066" + }, + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + } +### BlockScoutWeb.Account.Api.V1.UserController delete_custom_abi [DELETE /api/account/v1/user/custom_abis/{id}] + + + + ++ Parameters + + id: `145` + id: 145 + + ++ Request Delete custom abi +**DELETE**  `/api/account/v1/user/custom_abis/145` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMmQABWVtYWlsbQAAABp0ZXN0X3VzZXItMkBibG9ja3Njb3V0LmNvbWQAAmlkYgAAAQdkAARuYW1lbQAAAApVc2VyIFRlc3QyZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjJkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMmQADHdhdGNobGlzdF9pZGIAAAEH.xeXAG0XBVkoEw0SR5kJ04tyapR1tY5N9XTrN_nrO63c; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2XZv72akD4sAAAQk + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "message": "OK" + } +### BlockScoutWeb.Account.Api.V1.UserController create_public_tags_request [POST /api/account/v1/user/public_tags] + + + + + ++ Request Submit request to add a public tag +**POST**  `/api/account/v1/user/public_tags` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "website": "website11", + "tags": "Tag17", + "is_owner": false, + "full_name": "full name11", + "email": "test_user-24@blockscout.com", + "company": "company11", + "addresses": [ + "0x0000000000000000000000000000000000000067", + "0x0000000000000000000000000000000000000068", + "0x0000000000000000000000000000000000000069", + "0x000000000000000000000000000000000000006a", + "0x000000000000000000000000000000000000006b", + "0x000000000000000000000000000000000000006c", + "0x000000000000000000000000000000000000006d" + ], + "additional_comment": "additional_comment11" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIzQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABEWQABG5hbWVtAAAAC1VzZXIgVGVzdDEyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEyZAAMd2F0Y2hsaXN0X2lkYgAAARE.NJjO7QbBKV5g6_hGxLxBb5wlGDmJMKp-bpgLhhrFjLM; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2Zt2e1-7YrQAABVh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "website": "website11", + "tags": "Tag17", + "submission_date": "2022-12-03T16:55:29.441979Z", + "is_owner": false, + "id": 202, + "full_name": "full name11", + "email": "test_user-24@blockscout.com", + "company": "company11", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000067" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000068" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000069" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000006a" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000006b" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000006C" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000006D" + } + ], + "addresses": [ + "0x0000000000000000000000000000000000000067", + "0x0000000000000000000000000000000000000068", + "0x0000000000000000000000000000000000000069", + "0x000000000000000000000000000000000000006a", + "0x000000000000000000000000000000000000006b", + "0x000000000000000000000000000000000000006c", + "0x000000000000000000000000000000000000006d" + ], + "additional_comment": "additional_comment11" + } +### BlockScoutWeb.Account.Api.V1.UserController public_tags_requests [GET /api/account/v1/user/public_tags] + + + + + ++ Request Get list of requests to add a public tag +**GET**  `/api/account/v1/user/public_tags` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyM2QABWVtYWlsbQAAABp0ZXN0X3VzZXItM0BibG9ja3Njb3V0LmNvbWQAAmlkYgAAAQhkAARuYW1lbQAAAApVc2VyIFRlc3QzZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjNkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwM2QADHdhdGNobGlzdF9pZGIAAAEI.-a6kcGlCbsFgQtwPNaGA4yaOOpSpyG_54rEROF3a6E0; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2YJiDacnhiAAAA9h + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "website": "website9", + "tags": "Tag14", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": false, + "id": 200, + "full_name": "full name9", + "email": "test_user-13@blockscout.com", + "company": "company9", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000003D" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000003e" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000003f" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000040" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000041" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000042" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000043" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000044" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000045" + } + ], + "addresses": [ + "0x000000000000000000000000000000000000003d", + "0x000000000000000000000000000000000000003e", + "0x000000000000000000000000000000000000003f", + "0x0000000000000000000000000000000000000040", + "0x0000000000000000000000000000000000000041", + "0x0000000000000000000000000000000000000042", + "0x0000000000000000000000000000000000000043", + "0x0000000000000000000000000000000000000044", + "0x0000000000000000000000000000000000000045" + ], + "additional_comment": "additional_comment9" + }, + { + "website": "website8", + "tags": "Tag13", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": false, + "id": 199, + "full_name": "full name8", + "email": "test_user-12@blockscout.com", + "company": "company8", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000003a" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000003b" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000003c" + } + ], + "addresses": [ + "0x000000000000000000000000000000000000003a", + "0x000000000000000000000000000000000000003b", + "0x000000000000000000000000000000000000003c" + ], + "additional_comment": "additional_comment8" + }, + { + "website": "website7", + "tags": "Tag11;Tag12", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": true, + "id": 198, + "full_name": "full name7", + "email": "test_user-11@blockscout.com", + "company": "company7", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000032" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000033" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000034" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000035" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000036" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000037" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000038" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000039" + } + ], + "addresses": [ + "0x0000000000000000000000000000000000000032", + "0x0000000000000000000000000000000000000033", + "0x0000000000000000000000000000000000000034", + "0x0000000000000000000000000000000000000035", + "0x0000000000000000000000000000000000000036", + "0x0000000000000000000000000000000000000037", + "0x0000000000000000000000000000000000000038", + "0x0000000000000000000000000000000000000039" + ], + "additional_comment": "additional_comment7" + }, + { + "website": "website6", + "tags": "Tag10", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": true, + "id": 197, + "full_name": "full name6", + "email": "test_user-10@blockscout.com", + "company": "company6", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000002c" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000002D" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000002E" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000002F" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000030" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000031" + } + ], + "addresses": [ + "0x000000000000000000000000000000000000002c", + "0x000000000000000000000000000000000000002d", + "0x000000000000000000000000000000000000002e", + "0x000000000000000000000000000000000000002f", + "0x0000000000000000000000000000000000000030", + "0x0000000000000000000000000000000000000031" + ], + "additional_comment": "additional_comment6" + }, + { + "website": "website5", + "tags": "Tag9", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": true, + "id": 196, + "full_name": "full name5", + "email": "test_user-9@blockscout.com", + "company": "company5", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000028" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000029" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000002A" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000002b" + } + ], + "addresses": [ + "0x0000000000000000000000000000000000000028", + "0x0000000000000000000000000000000000000029", + "0x000000000000000000000000000000000000002a", + "0x000000000000000000000000000000000000002b" + ], + "additional_comment": "additional_comment5" + }, + { + "website": "website4", + "tags": "Tag7;Tag8", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": false, + "id": 195, + "full_name": "full name4", + "email": "test_user-8@blockscout.com", + "company": "company4", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000020" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000021" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000022" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000023" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000024" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000025" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000026" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000027" + } + ], + "addresses": [ + "0x0000000000000000000000000000000000000020", + "0x0000000000000000000000000000000000000021", + "0x0000000000000000000000000000000000000022", + "0x0000000000000000000000000000000000000023", + "0x0000000000000000000000000000000000000024", + "0x0000000000000000000000000000000000000025", + "0x0000000000000000000000000000000000000026", + "0x0000000000000000000000000000000000000027" + ], + "additional_comment": "additional_comment4" + }, + { + "website": "website3", + "tags": "Tag5;Tag6", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": true, + "id": 194, + "full_name": "full name3", + "email": "test_user-7@blockscout.com", + "company": "company3", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000001a" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000001B" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000001c" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000001D" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000001e" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000001F" + } + ], + "addresses": [ + "0x000000000000000000000000000000000000001a", + "0x000000000000000000000000000000000000001b", + "0x000000000000000000000000000000000000001c", + "0x000000000000000000000000000000000000001d", + "0x000000000000000000000000000000000000001e", + "0x000000000000000000000000000000000000001f" + ], + "additional_comment": "additional_comment3" + }, + { + "website": "website2", + "tags": "Tag3;Tag4", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": true, + "id": 193, + "full_name": "full name2", + "email": "test_user-6@blockscout.com", + "company": "company2", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000010" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000011" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000012" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000013" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000014" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000015" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000016" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000017" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000018" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000019" + } + ], + "addresses": [ + "0x0000000000000000000000000000000000000010", + "0x0000000000000000000000000000000000000011", + "0x0000000000000000000000000000000000000012", + "0x0000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000014", + "0x0000000000000000000000000000000000000015", + "0x0000000000000000000000000000000000000016", + "0x0000000000000000000000000000000000000017", + "0x0000000000000000000000000000000000000018", + "0x0000000000000000000000000000000000000019" + ], + "additional_comment": "additional_comment2" + }, + { + "website": "website1", + "tags": "Tag2", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": false, + "id": 192, + "full_name": "full name1", + "email": "test_user-5@blockscout.com", + "company": "company1", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000000E" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000000F" + } + ], + "addresses": [ + "0x000000000000000000000000000000000000000e", + "0x000000000000000000000000000000000000000f" + ], + "additional_comment": "additional_comment1" + }, + { + "website": "website0", + "tags": "Tag0;Tag1", + "submission_date": "2022-12-03T16:55:29.000000Z", + "is_owner": true, + "id": 191, + "full_name": "full name0", + "email": "test_user-4@blockscout.com", + "company": "company0", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000008" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000009" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000000A" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000000b" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000000C" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000000d" + } + ], + "addresses": [ + "0x0000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000c", + "0x000000000000000000000000000000000000000d" + ], + "additional_comment": "additional_comment0" + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_public_tags_request [DELETE /api/account/v1/user/public_tags/{id}] + + + + ++ Parameters + + id: `200` + id: 200 + + ++ Request Delete public tags request +**DELETE**  `/api/account/v1/user/public_tags/200` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "remove_reason": "reason" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyM2QABWVtYWlsbQAAABp0ZXN0X3VzZXItM0BibG9ja3Njb3V0LmNvbWQAAmlkYgAAAQhkAARuYW1lbQAAAApVc2VyIFRlc3QzZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjNkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwM2QADHdhdGNobGlzdF9pZGIAAAEI.-a6kcGlCbsFgQtwPNaGA4yaOOpSpyG_54rEROF3a6E0; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2YdEq9snhiAAAA-h + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "message": "OK" + } +### BlockScoutWeb.Account.Api.V1.UserController update_public_tags_request [PUT /api/account/v1/user/public_tags/{id}] + + + + ++ Parameters + + id: `203` + id: 203 + + ++ Request Edit request to add a public tag +**PUT**  `/api/account/v1/user/public_tags/203` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "website": "website13", + "tags": "Tag20;Tag21", + "is_owner": false, + "full_name": "full name13", + "email": "test_user-35@blockscout.com", + "company": "company13", + "addresses": [ + "0x0000000000000000000000000000000000000085", + "0x0000000000000000000000000000000000000086", + "0x0000000000000000000000000000000000000087", + "0x0000000000000000000000000000000000000088", + "0x0000000000000000000000000000000000000089", + "0x000000000000000000000000000000000000008a", + "0x000000000000000000000000000000000000008b" + ], + "additional_comment": "additional_comment13" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTMzQGJsb2Nrc2NvdXQuY29tZAACaWRiAAABGmQABG5hbWVtAAAAC1VzZXIgVGVzdDIxZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIxZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIxZAAMd2F0Y2hsaXN0X2lkYgAAARo.byLDQXd4VuN-Y1kqWEWSxe5Q_ne42ove8xpm5k_GwHc; path=/; SameSite=Lax + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: Fy1W2aqpFvr2fxsAABjB + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "website": "website13", + "tags": "Tag20;Tag21", + "submission_date": "2022-12-03T16:55:30.000000Z", + "is_owner": false, + "id": 203, + "full_name": "full name13", + "email": "test_user-35@blockscout.com", + "company": "company13", + "addresses_with_info": [ + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000085" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000086" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000087" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000088" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x0000000000000000000000000000000000000089" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000008A" + }, + { + "watchlist_names": [], + "public_tags": [], + "private_tags": [], + "name": null, + "is_verified": null, + "is_contract": false, + "implementation_name": null, + "hash": "0x000000000000000000000000000000000000008b" + } + ], + "addresses": [ + "0x0000000000000000000000000000000000000085", + "0x0000000000000000000000000000000000000086", + "0x0000000000000000000000000000000000000087", + "0x0000000000000000000000000000000000000088", + "0x0000000000000000000000000000000000000089", + "0x000000000000000000000000000000000000008a", + "0x000000000000000000000000000000000000008b" + ], + "additional_comment": "additional_comment13" + } + diff --git a/apps/block_scout_web/API.md b/apps/block_scout_web/API.md new file mode 100644 index 000000000000..afc68c19e65f --- /dev/null +++ b/apps/block_scout_web/API.md @@ -0,0 +1,2227 @@ +# API Documentation + + * [BlockScoutWeb.Account.Api.V1.UserController](#blockscoutweb-account-api-v1-usercontroller) + * [info](#blockscoutweb-account-api-v1-usercontroller-info) + * [create_tag_address](#blockscoutweb-account-api-v1-usercontroller-create_tag_address) + * [BlockScoutWeb.Account.Api.V1.TagsController](#blockscoutweb-account-api-v1-tagscontroller) + * [tags_address](#blockscoutweb-account-api-v1-tagscontroller-tags_address) + * [BlockScoutWeb.Account.Api.V1.UserController](#blockscoutweb-account-api-v1-usercontroller) + * [update_tag_address](#blockscoutweb-account-api-v1-usercontroller-update_tag_address) + * [tags_address](#blockscoutweb-account-api-v1-usercontroller-tags_address) + * [delete_tag_address](#blockscoutweb-account-api-v1-usercontroller-delete_tag_address) + * [create_tag_transaction](#blockscoutweb-account-api-v1-usercontroller-create_tag_transaction) + * [BlockScoutWeb.Account.Api.V1.TagsController](#blockscoutweb-account-api-v1-tagscontroller) + * [tags_transaction](#blockscoutweb-account-api-v1-tagscontroller-tags_transaction) + * [BlockScoutWeb.Account.Api.V1.UserController](#blockscoutweb-account-api-v1-usercontroller) + * [update_tag_transaction](#blockscoutweb-account-api-v1-usercontroller-update_tag_transaction) + * [tags_transaction](#blockscoutweb-account-api-v1-usercontroller-tags_transaction) + * [delete_tag_transaction](#blockscoutweb-account-api-v1-usercontroller-delete_tag_transaction) + * [create_watchlist](#blockscoutweb-account-api-v1-usercontroller-create_watchlist) + * [watchlist](#blockscoutweb-account-api-v1-usercontroller-watchlist) + * [delete_watchlist](#blockscoutweb-account-api-v1-usercontroller-delete_watchlist) + * [update_watchlist](#blockscoutweb-account-api-v1-usercontroller-update_watchlist) + * [create_watchlist](#blockscoutweb-account-api-v1-usercontroller-create_watchlist) + * [update_watchlist](#blockscoutweb-account-api-v1-usercontroller-update_watchlist) + * [create_api_key](#blockscoutweb-account-api-v1-usercontroller-create_api_key) + * [api_keys](#blockscoutweb-account-api-v1-usercontroller-api_keys) + * [update_api_key](#blockscoutweb-account-api-v1-usercontroller-update_api_key) + * [delete_api_key](#blockscoutweb-account-api-v1-usercontroller-delete_api_key) + * [create_custom_abi](#blockscoutweb-account-api-v1-usercontroller-create_custom_abi) + * [custom_abis](#blockscoutweb-account-api-v1-usercontroller-custom_abis) + * [update_custom_abi](#blockscoutweb-account-api-v1-usercontroller-update_custom_abi) + * [delete_custom_abi](#blockscoutweb-account-api-v1-usercontroller-delete_custom_abi) + * [create_public_tags_request](#blockscoutweb-account-api-v1-usercontroller-create_public_tags_request) + * [public_tags_requests](#blockscoutweb-account-api-v1-usercontroller-public_tags_requests) + * [delete_public_tags_request](#blockscoutweb-account-api-v1-usercontroller-delete_public_tags_request) + * [update_public_tags_request](#blockscoutweb-account-api-v1-usercontroller-update_public_tags_request) + +## BlockScoutWeb.Account.Api.V1.UserController +### info +#### Get info about user + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/user/info + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNGQABWVtYWlsbQAAABp0ZXN0X3VzZXItNEBibG9ja3Njb3V0LmNvbWQAAmlkYcRkAARuYW1lbQAAAApVc2VyIFRlc3Q0ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjRkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwNGQADHdhdGNobGlzdF9pZGHE.Ovcc2Vzzv4fhFzmirtQjJ06gcqQwUHMMlju7VX24fyo; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y1_QfU9-YaIAAGdh +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "nickname": "test_user4", + "name": "User Test4", + "email": "test_user-4@blockscout.com", + "avatar": "https://example.com/avatar/test_user4" +} +``` + +### create_tag_address +#### Add private address tag + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/tags/address +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "MyName", + "address_hash": "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMThkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIyQGJsb2Nrc2NvdXQuY29tZAACaWRh0mQABG5hbWVtAAAAC1VzZXIgVGVzdDE4ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE4ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE4ZAAMd2F0Y2hsaXN0X2lkYdI.tFFJ387fBBdBFuMzzeaWcMTeapzMHnbuEfnqTdq5lJ8; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3ALw8xSCMAAAHAC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "name": "MyName", + "id": 61, + "address_hash": "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b" +} +``` + +## BlockScoutWeb.Account.Api.V1.TagsController +### tags_address +#### Get tags for address + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMThkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIyQGJsb2Nrc2NvdXQuY29tZAACaWRh0mQABG5hbWVtAAAAC1VzZXIgVGVzdDE4ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE4ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE4ZAAMd2F0Y2hsaXN0X2lkYdI.tFFJ387fBBdBFuMzzeaWcMTeapzMHnbuEfnqTdq5lJ8; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3BIWjdSCMAAAG4B +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "watchlist_names": [], + "personal_tags": [ + { + "label": "MyName", + "display_name": "MyName", + "address_hash": "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b" + } + ], + "common_tags": [] +} +``` + +## BlockScoutWeb.Account.Api.V1.UserController +### update_tag_address +#### Edit private address tag + +##### Request +* __Method:__ PUT +* __Path:__ /api/account/v1/user/tags/address/57 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "name3", + "address_hash": "0x0000000000000000000000000000000000000016" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyN2QABWVtYWlsbQAAABt0ZXN0X3VzZXItMTBAYmxvY2tzY291dC5jb21kAAJpZGHHZAAEbmFtZW0AAAAKVXNlciBUZXN0N2QACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI3ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDdkAAx3YXRjaGxpc3RfaWRhxw.Bn03yTZrlP0m6amYLQVeI-pvhvUf1F6d9SGAkDTLEck; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2IdgOjzsTkAAGYC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "name": "name3", + "id": 57, + "address_hash": "0x0000000000000000000000000000000000000016" +} +``` + +### tags_address +#### Get private addresses tags + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/user/tags/address + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTVkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE5QGJsb2Nrc2NvdXQuY29tZAACaWRhz2QABG5hbWVtAAAAC1VzZXIgVGVzdDE1ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE1ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE1ZAAMd2F0Y2hsaXN0X2lkYc8.AoYBq7uUH9JOt11vL4-71qtsXMzpPDFsx8BV97n1Y-o; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2ynKDFWAsYAAG5C +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +[ + { + "name": "name2", + "id": 60, + "address_hash": "0x000000000000000000000000000000000000003f" + }, + { + "name": "name1", + "id": 59, + "address_hash": "0x000000000000000000000000000000000000003e" + }, + { + "name": "name0", + "id": 58, + "address_hash": "0x000000000000000000000000000000000000003d" + } +] +``` + +### delete_tag_address +#### Delete private address tag + +##### Request +* __Method:__ DELETE +* __Path:__ /api/account/v1/user/tags/address/62 + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjRkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTM4QGJsb2Nrc2NvdXQuY29tZAACaWRh2GQABG5hbWVtAAAAC1VzZXIgVGVzdDI0ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjI0ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDI0ZAAMd2F0Y2hsaXN0X2lkYdg.x6Qf5zC5gCGQrKy2MbTqd3Xt7S_2oUYaCnO-pbZwRMI; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3biZmVZE0MAAHKC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "message": "OK" +} +``` + +### create_tag_transaction +#### Error on try to create private transaction tag for tx does not exist + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/tags/transaction +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000008", + "name": "MyName" +} +``` + +##### Response +* __Status__: 422 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTlkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIzQGJsb2Nrc2NvdXQuY29tZAACaWRh02QABG5hbWVtAAAAC1VzZXIgVGVzdDE5ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE5ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE5ZAAMd2F0Y2hsaXN0X2lkYdM.zuwR-sOIcF7Xpo97W6G9Szzi_BPlu6Pu9_4kn7T2c10; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3DXWVBu-HUAAG6h +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "errors": { + "tx_hash": [ + "Transaction does not exist" + ] + } +} +``` + +#### Create private transaction tag + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/tags/transaction +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000009", + "name": "MyName" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTlkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIzQGJsb2Nrc2NvdXQuY29tZAACaWRh02QABG5hbWVtAAAAC1VzZXIgVGVzdDE5ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE5ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE5ZAAMd2F0Y2hsaXN0X2lkYdM.zuwR-sOIcF7Xpo97W6G9Szzi_BPlu6Pu9_4kn7T2c10; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3EB0Ytu-HUAAG7B +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000009", + "name": "MyName", + "id": 64 +} +``` + +## BlockScoutWeb.Account.Api.V1.TagsController +### tags_transaction +#### Get tags for transaction + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/tags/transaction/0x0000000000000000000000000000000000000000000000000000000000000009 + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTlkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIzQGJsb2Nrc2NvdXQuY29tZAACaWRh02QABG5hbWVtAAAAC1VzZXIgVGVzdDE5ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE5ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE5ZAAMd2F0Y2hsaXN0X2lkYdM.zuwR-sOIcF7Xpo97W6G9Szzi_BPlu6Pu9_4kn7T2c10; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3Efe0tu-HUAAG7h +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "watchlist_names": [], + "personal_tx_tag": { + "label": "MyName" + }, + "personal_tags": [], + "common_tags": [] +} +``` + +## BlockScoutWeb.Account.Api.V1.UserController +### update_tag_transaction +#### Edit private transaction tag + +##### Request +* __Method:__ PUT +* __Path:__ /api/account/v1/user/tags/transaction/57 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "name": "name1" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMGQABWVtYWlsbQAAABp0ZXN0X3VzZXItMEBibG9ja3Njb3V0LmNvbWQAAmlkYcBkAARuYW1lbQAAAApVc2VyIFRlc3QwZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjBkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMGQADHdhdGNobGlzdF9pZGHA.-aMP6TTEeEfxopoeChJPvTvjkSRD9_ZgaeLDlOC21gU; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y1xoENHeIlkAAGEi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "name": "name1", + "id": 57 +} +``` + +### tags_transaction +#### Get private transactions tags + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/user/tags/transaction + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTRkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE4QGJsb2Nrc2NvdXQuY29tZAACaWRhzmQABG5hbWVtAAAAC1VzZXIgVGVzdDE0ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE0ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE0ZAAMd2F0Y2hsaXN0X2lkYc4.8SGhlMOY4aB444Afz1VajofmGp9YZbrfbVkZ4BTyaBI; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2tEsVp5P30AAGzi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +[ + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000004", + "name": "name2", + "id": 60 + }, + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000003", + "name": "name1", + "id": 59 + }, + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "name": "name0", + "id": 58 + } +] +``` + +### delete_tag_transaction +#### Delete private transaction tag + +##### Request +* __Method:__ DELETE +* __Path:__ /api/account/v1/user/tags/transaction/61 + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTZkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIwQGJsb2Nrc2NvdXQuY29tZAACaWRh0GQABG5hbWVtAAAAC1VzZXIgVGVzdDE2ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE2ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE2ZAAMd2F0Y2hsaXN0X2lkYdA.YfL9L7-UIBleRbWWhHNvutNuw8Y4SadvwGFmGwakxQA; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y26c9UuC4TcAAGwh +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "message": "OK" +} +``` + +### create_watchlist +#### Add address to watch list + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/watchlist +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "notification_settings": { + "native": { + "outcoming": false, + "incoming": true + }, + "ERC-721": { + "outcoming": false, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": false + } + }, + "notification_methods": { + "email": true + }, + "name": "test2", + "address_hash": "0x0000000000000000000000000000000000000007" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyM2QABWVtYWlsbQAAABp0ZXN0X3VzZXItM0BibG9ja3Njb3V0LmNvbWQAAmlkYcNkAARuYW1lbQAAAApVc2VyIFRlc3QzZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjNkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwM2QADHdhdGNobGlzdF9pZGHD.kv5nnz8sVGLaopoZs9ppOfu0hfpFi58yuisPDN6PtPI; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y16Kv_0GzWcAAGKi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "notification_settings": { + "native": { + "outcoming": false, + "incoming": true + }, + "ERC-721": { + "outcoming": false, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": false + } + }, + "notification_methods": { + "email": true + }, + "name": "test2", + "id": 68, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000007", + "address_balance": null +} +``` + +### watchlist +#### Get addresses from watchlists + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/user/watchlist + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyM2QABWVtYWlsbQAAABp0ZXN0X3VzZXItM0BibG9ja3Njb3V0LmNvbWQAAmlkYcNkAARuYW1lbQAAAApVc2VyIFRlc3QzZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjNkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwM2QADHdhdGNobGlzdF9pZGHD.kv5nnz8sVGLaopoZs9ppOfu0hfpFi58yuisPDN6PtPI; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y19FyIUGzWcAAGMC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +[ + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": false + } + }, + "notification_methods": { + "email": false + }, + "name": "test3", + "id": 69, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000008", + "address_balance": null + }, + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": true + }, + "ERC-721": { + "outcoming": false, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": false + } + }, + "notification_methods": { + "email": true + }, + "name": "test2", + "id": 68, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000007", + "address_balance": null + } +] +``` + +### delete_watchlist +#### Delete address from watchlist by id + +##### Request +* __Method:__ DELETE +* __Path:__ /api/account/v1/user/watchlist/74 + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE0QGJsb2Nrc2NvdXQuY29tZAACaWRhy2QABG5hbWVtAAAAC1VzZXIgVGVzdDExZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjExZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDExZAAMd2F0Y2hsaXN0X2lkYcs.YjW8nzuA66id0ADg2qpyjTMGfKJ7BHhjU_HdVq8w8vk; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2f5j2WpY30AAGuC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "message": "OK" +} +``` + +### update_watchlist +#### Edit watchlist address + +##### Request +* __Method:__ PUT +* __Path:__ /api/account/v1/user/watchlist/67 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "notification_settings": { + "native": { + "outcoming": false, + "incoming": true + }, + "ERC-721": { + "outcoming": true, + "incoming": true + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": true + }, + "name": "test1", + "address_hash": "0x0000000000000000000000000000000000000006" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMWQABWVtYWlsbQAAABp0ZXN0X3VzZXItMUBibG9ja3Njb3V0LmNvbWQAAmlkYcFkAARuYW1lbQAAAApVc2VyIFRlc3QxZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjFkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMWQADHdhdGNobGlzdF9pZGHB.3KOkZkPrcMrRXfooQckn-zi6xmax1LJMBGBSjmGM8ww; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y12FoNKu97sAAGch +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "notification_settings": { + "native": { + "outcoming": false, + "incoming": true + }, + "ERC-721": { + "outcoming": true, + "incoming": true + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": true + }, + "name": "test1", + "id": 67, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000006", + "address_balance": null +} +``` + +### create_watchlist +#### Example of error on creating watchlist address + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/watchlist +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "notification_settings": { + "native": { + "outcoming": false, + "incoming": true + }, + "ERC-721": { + "outcoming": false, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": false + } + }, + "notification_methods": { + "email": false + }, + "name": "test4", + "address_hash": "0x0000000000000000000000000000000000000017" +} +``` + +##### Response +* __Status__: 422 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyOGQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTFAYmxvY2tzY291dC5jb21kAAJpZGHIZAAEbmFtZW0AAAAKVXNlciBUZXN0OGQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI4ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDhkAAx3YXRjaGxpc3RfaWRhyA.q1Rmte0qLd31GbmpA46bE8rXo2okwzX8aD_oDHn8CIQ; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2MCqHvooPMAAGbi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "errors": { + "watchlist_id": [ + "Address already added to the watch list" + ] + } +} +``` + +### update_watchlist +#### Example of error on editing watchlist address + +##### Request +* __Method:__ PUT +* __Path:__ /api/account/v1/user/watchlist/72 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "notification_settings": { + "native": { + "outcoming": false, + "incoming": true + }, + "ERC-721": { + "outcoming": false, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": false + } + }, + "notification_methods": { + "email": false + }, + "name": "test4", + "address_hash": "0x0000000000000000000000000000000000000017" +} +``` + +##### Response +* __Status__: 422 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyOGQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTFAYmxvY2tzY291dC5jb21kAAJpZGHIZAAEbmFtZW0AAAAKVXNlciBUZXN0OGQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI4ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDhkAAx3YXRjaGxpc3RfaWRhyA.q1Rmte0qLd31GbmpA46bE8rXo2okwzX8aD_oDHn8CIQ; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2Nh1eHooPMAAGci +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "errors": { + "watchlist_id": [ + "Address already added to the watch list" + ] + } +} +``` + +### create_api_key +#### Add api key + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/api_keys +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "test" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMmQABWVtYWlsbQAAABp0ZXN0X3VzZXItMkBibG9ja3Njb3V0LmNvbWQAAmlkYcJkAARuYW1lbQAAAApVc2VyIFRlc3QyZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjJkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMmQADHdhdGNobGlzdF9pZGHC.ULESD1_sOySz8eEVGnagUzGw6eMIx_8Pwoyr_5S3K0M; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y14XlMBqXaQAAGHi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "name": "test", + "api_key": "de9ef457-3f47-48d3-affa-79ad9d3b27b9" +} +``` + +#### Example of error on creating api key + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/api_keys +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "test" +} +``` + +##### Response +* __Status__: 422 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI2QGJsb2Nrc2NvdXQuY29tZAACaWRh1mQABG5hbWVtAAAAC1VzZXIgVGVzdDIyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIyZAAMd2F0Y2hsaXN0X2lkYdY.P37J2lZZdHaT4P-RatVaXCx77UcSH3s_TMx-FieaYk0; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3LmuuofZKYAAG_h +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "errors": { + "name": [ + "Max 3 keys per account" + ] + } +} +``` + +### api_keys +#### Get api keys list + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/user/api_keys + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI2QGJsb2Nrc2NvdXQuY29tZAACaWRh1mQABG5hbWVtAAAAC1VzZXIgVGVzdDIyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIyZAAMd2F0Y2hsaXN0X2lkYdY.P37J2lZZdHaT4P-RatVaXCx77UcSH3s_TMx-FieaYk0; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3LyOSIfZKYAAHAB +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +[ + { + "name": "test", + "api_key": "2ac16688-34e6-4fa4-8983-a9bc34c912f6" + }, + { + "name": "test", + "api_key": "a55426db-04f0-40be-a146-1ced4558aa0c" + }, + { + "name": "test", + "api_key": "d73fc23b-59f0-4e6f-a739-f4de30995101" + } +] +``` + +### update_api_key +#### Edit api key + +##### Request +* __Method:__ PUT +* __Path:__ /api/account/v1/user/api_keys/2b1d400d-713e-4bfc-8ef0-710555693138 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "test_1" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTdkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIxQGJsb2Nrc2NvdXQuY29tZAACaWRh0WQABG5hbWVtAAAAC1VzZXIgVGVzdDE3ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE3ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE3ZAAMd2F0Y2hsaXN0X2lkYdE.bLJKM3-kFm04mMC-4-3b2mjrig_lmQYt5C2tg-9q9so; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2-0eR7T2BMAAG0B +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "name": "test_1", + "api_key": "2b1d400d-713e-4bfc-8ef0-710555693138" +} +``` + +### delete_api_key +#### Delete api key + +##### Request +* __Method:__ DELETE +* __Path:__ /api/account/v1/user/api_keys/3bd44c0d-290f-4dfc-9283-5f674080f8ef + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjBkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI0QGJsb2Nrc2NvdXQuY29tZAACaWRh1GQABG5hbWVtAAAAC1VzZXIgVGVzdDIwZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIwZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIwZAAMd2F0Y2hsaXN0X2lkYdQ.WgjMmOxwwBGcTZZscpLA8EXErwL8ITCvoIXPLIQAhtw; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3HQdpa0710AAHBi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "message": "OK" +} +``` + +### create_custom_abi +#### Add custom abi + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/custom_abis +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "test25", + "contract_address_hash": "0x000000000000000000000000000000000000002c", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE1QGJsb2Nrc2NvdXQuY29tZAACaWRhzGQABG5hbWVtAAAAC1VzZXIgVGVzdDEyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEyZAAMd2F0Y2hsaXN0X2lkYcw.7cCOt6SVrOb5VLYplBzwZ03FWMo9jQpAV7cNroY4txY; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2iZJWbZgfgAAGwC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "name": "test25", + "id": 143, + "contract_address_hash": "0x000000000000000000000000000000000000002c", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] +} +``` + +#### Example of error on creating custom abi + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/custom_abis +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "test21", + "contract_address_hash": "0x0000000000000000000000000000000000000028", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] +} +``` + +##### Response +* __Status__: 422 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyOWQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTJAYmxvY2tzY291dC5jb21kAAJpZGHJZAAEbmFtZW0AAAAKVXNlciBUZXN0OWQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI5ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDlkAAx3YXRjaGxpc3RfaWRhyQ.MCpJsS-nb95ccHRtzOk7DbIRjEcTG34ONq4PrC5hOcU; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2Ypm-ny0swAAGiB +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "errors": { + "name": [ + "Max 15 ABIs per account" + ] + } +} +``` + +### custom_abis +#### Get custom abis list + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/user/custom_abis + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyOWQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTJAYmxvY2tzY291dC5jb21kAAJpZGHJZAAEbmFtZW0AAAAKVXNlciBUZXN0OWQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI5ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDlkAAx3YXRjaGxpc3RfaWRhyQ.MCpJsS-nb95ccHRtzOk7DbIRjEcTG34ONq4PrC5hOcU; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2Y-qjXy0swAAGnC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +[ + { + "name": "test20", + "id": 141, + "contract_address_hash": "0x0000000000000000000000000000000000000027", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test19", + "id": 140, + "contract_address_hash": "0x0000000000000000000000000000000000000026", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test18", + "id": 139, + "contract_address_hash": "0x0000000000000000000000000000000000000025", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test17", + "id": 138, + "contract_address_hash": "0x0000000000000000000000000000000000000024", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test16", + "id": 137, + "contract_address_hash": "0x0000000000000000000000000000000000000023", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test15", + "id": 136, + "contract_address_hash": "0x0000000000000000000000000000000000000022", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test14", + "id": 135, + "contract_address_hash": "0x0000000000000000000000000000000000000021", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test13", + "id": 134, + "contract_address_hash": "0x0000000000000000000000000000000000000020", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test12", + "id": 133, + "contract_address_hash": "0x000000000000000000000000000000000000001f", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test11", + "id": 132, + "contract_address_hash": "0x000000000000000000000000000000000000001e", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test10", + "id": 131, + "contract_address_hash": "0x000000000000000000000000000000000000001d", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test9", + "id": 130, + "contract_address_hash": "0x000000000000000000000000000000000000001c", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test8", + "id": 129, + "contract_address_hash": "0x000000000000000000000000000000000000001b", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test7", + "id": 128, + "contract_address_hash": "0x000000000000000000000000000000000000001a", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + }, + { + "name": "test6", + "id": 127, + "contract_address_hash": "0x0000000000000000000000000000000000000019", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] + } +] +``` + +### update_custom_abi +#### Edit custom abi + +##### Request +* __Method:__ PUT +* __Path:__ /api/account/v1/user/custom_abis/144 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "name": "test27", + "contract_address_hash": "0x000000000000000000000000000000000000004b", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI1QGJsb2Nrc2NvdXQuY29tZAACaWRh1WQABG5hbWVtAAAAC1VzZXIgVGVzdDIxZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIxZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIxZAAMd2F0Y2hsaXN0X2lkYdU.SEUqq9ZiSD79HIzwKvwTspmBKKU87m_Xwu5gw2pX1e0; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3JcHmB4X2AAAHDC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "name": "test27", + "id": 144, + "contract_address_hash": "0x000000000000000000000000000000000000004b", + "abi": [ + { + "type": "function", + "stateMutability": "nonpayable", + "payable": false, + "outputs": [], + "name": "set", + "inputs": [ + { + "type": "uint256", + "name": "x" + } + ], + "constant": false + }, + { + "type": "function", + "stateMutability": "view", + "payable": false, + "outputs": [ + { + "type": "uint256", + "name": "" + } + ], + "name": "get", + "inputs": [], + "constant": true + } + ] +} +``` + +### delete_custom_abi +#### Delete custom abi + +##### Request +* __Method:__ DELETE +* __Path:__ /api/account/v1/user/custom_abis/142 + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTBkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTEzQGJsb2Nrc2NvdXQuY29tZAACaWRhymQABG5hbWVtAAAAC1VzZXIgVGVzdDEwZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEwZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEwZAAMd2F0Y2hsaXN0X2lkYco.x_6dmEjpZ1o8_ct-M7pWWP0LkI66xhwl8gWeQt9XzHA; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2b1jJGBaO4AAGrC +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "message": "OK" +} +``` + +### create_public_tags_request +#### Submit request to add a public tag + +##### Request +* __Method:__ POST +* __Path:__ /api/account/v1/user/public_tags +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "website": "website0", + "tags": "Tag0", + "is_owner": true, + "full_name": "full name0", + "email": "test_user-6@blockscout.com", + "company": "company0", + "addresses": [ + "0x0000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000c", + "0x000000000000000000000000000000000000000d" + ], + "additional_comment": "additional_comment0" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNWQABWVtYWlsbQAAABp0ZXN0X3VzZXItNUBibG9ja3Njb3V0LmNvbWQAAmlkYcVkAARuYW1lbQAAAApVc2VyIFRlc3Q1ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjVkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwNWQADHdhdGNobGlzdF9pZGHF.kXAMBaL9a7aYjPDgZ9Llxe1etUCPH3vEvQe9Fq2May4; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2BIESA-ecUAAGgB +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "website": "website0", + "tags": "Tag0", + "submission_date": "2022-09-03T21:00:07.156465Z", + "is_owner": true, + "id": 131, + "full_name": "full name0", + "email": "test_user-6@blockscout.com", + "company": "company0", + "addresses": [ + "0x0000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000c", + "0x000000000000000000000000000000000000000d" + ], + "additional_comment": "additional_comment0" +} +``` + +### public_tags_requests +#### Get list of requests to add a public tag + +##### Request +* __Method:__ GET +* __Path:__ /api/account/v1/user/public_tags + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjNkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI3QGJsb2Nrc2NvdXQuY29tZAACaWRh12QABG5hbWVtAAAAC1VzZXIgVGVzdDIzZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIzZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIzZAAMd2F0Y2hsaXN0X2lkYdc._6gJnvzjA6VEztgoIdpp7chhmhsdFrJImlcdrp4-pW0; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3SaPVCdkicAAHIi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +[ + { + "website": "website13", + "tags": "Tag17", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": false, + "id": 143, + "full_name": "full name13", + "email": "test_user-37@blockscout.com", + "company": "company13", + "addresses": [ + "0x000000000000000000000000000000000000007e", + "0x000000000000000000000000000000000000007f", + "0x0000000000000000000000000000000000000080", + "0x0000000000000000000000000000000000000081", + "0x0000000000000000000000000000000000000082", + "0x0000000000000000000000000000000000000083", + "0x0000000000000000000000000000000000000084" + ], + "additional_comment": "additional_comment13" + }, + { + "website": "website12", + "tags": "Tag16", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": false, + "id": 142, + "full_name": "full name12", + "email": "test_user-36@blockscout.com", + "company": "company12", + "addresses": [ + "0x0000000000000000000000000000000000000075", + "0x0000000000000000000000000000000000000076", + "0x0000000000000000000000000000000000000077", + "0x0000000000000000000000000000000000000078", + "0x0000000000000000000000000000000000000079", + "0x000000000000000000000000000000000000007a", + "0x000000000000000000000000000000000000007b", + "0x000000000000000000000000000000000000007c", + "0x000000000000000000000000000000000000007d" + ], + "additional_comment": "additional_comment12" + }, + { + "website": "website11", + "tags": "Tag15", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": false, + "id": 141, + "full_name": "full name11", + "email": "test_user-35@blockscout.com", + "company": "company11", + "addresses": [ + "0x000000000000000000000000000000000000006d", + "0x000000000000000000000000000000000000006e", + "0x000000000000000000000000000000000000006f", + "0x0000000000000000000000000000000000000070", + "0x0000000000000000000000000000000000000071", + "0x0000000000000000000000000000000000000072", + "0x0000000000000000000000000000000000000073", + "0x0000000000000000000000000000000000000074" + ], + "additional_comment": "additional_comment11" + }, + { + "website": "website10", + "tags": "Tag14", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": false, + "id": 140, + "full_name": "full name10", + "email": "test_user-34@blockscout.com", + "company": "company10", + "addresses": [ + "0x0000000000000000000000000000000000000067", + "0x0000000000000000000000000000000000000068", + "0x0000000000000000000000000000000000000069", + "0x000000000000000000000000000000000000006a", + "0x000000000000000000000000000000000000006b", + "0x000000000000000000000000000000000000006c" + ], + "additional_comment": "additional_comment10" + }, + { + "website": "website9", + "tags": "Tag13", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": true, + "id": 139, + "full_name": "full name9", + "email": "test_user-33@blockscout.com", + "company": "company9", + "addresses": [ + "0x0000000000000000000000000000000000000061", + "0x0000000000000000000000000000000000000062", + "0x0000000000000000000000000000000000000063", + "0x0000000000000000000000000000000000000064", + "0x0000000000000000000000000000000000000065", + "0x0000000000000000000000000000000000000066" + ], + "additional_comment": "additional_comment9" + }, + { + "website": "website8", + "tags": "Tag12", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": false, + "id": 138, + "full_name": "full name8", + "email": "test_user-32@blockscout.com", + "company": "company8", + "addresses": [ + "0x0000000000000000000000000000000000000060" + ], + "additional_comment": "additional_comment8" + }, + { + "website": "website7", + "tags": "Tag11", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": true, + "id": 137, + "full_name": "full name7", + "email": "test_user-31@blockscout.com", + "company": "company7", + "addresses": [ + "0x000000000000000000000000000000000000005f" + ], + "additional_comment": "additional_comment7" + }, + { + "website": "website6", + "tags": "Tag9;Tag10", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": true, + "id": 136, + "full_name": "full name6", + "email": "test_user-30@blockscout.com", + "company": "company6", + "addresses": [ + "0x000000000000000000000000000000000000005a", + "0x000000000000000000000000000000000000005b", + "0x000000000000000000000000000000000000005c", + "0x000000000000000000000000000000000000005d", + "0x000000000000000000000000000000000000005e" + ], + "additional_comment": "additional_comment6" + }, + { + "website": "website5", + "tags": "Tag8", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": false, + "id": 135, + "full_name": "full name5", + "email": "test_user-29@blockscout.com", + "company": "company5", + "addresses": [ + "0x0000000000000000000000000000000000000051", + "0x0000000000000000000000000000000000000052", + "0x0000000000000000000000000000000000000053", + "0x0000000000000000000000000000000000000054", + "0x0000000000000000000000000000000000000055", + "0x0000000000000000000000000000000000000056", + "0x0000000000000000000000000000000000000057", + "0x0000000000000000000000000000000000000058", + "0x0000000000000000000000000000000000000059" + ], + "additional_comment": "additional_comment5" + }, + { + "website": "website4", + "tags": "Tag6;Tag7", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": true, + "id": 134, + "full_name": "full name4", + "email": "test_user-28@blockscout.com", + "company": "company4", + "addresses": [ + "0x000000000000000000000000000000000000004c", + "0x000000000000000000000000000000000000004d", + "0x000000000000000000000000000000000000004e", + "0x000000000000000000000000000000000000004f", + "0x0000000000000000000000000000000000000050" + ], + "additional_comment": "additional_comment4" + } +] +``` + +### delete_public_tags_request +#### Delete public tags request + +##### Request +* __Method:__ DELETE +* __Path:__ /api/account/v1/user/public_tags/143 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "remove_reason": "reason" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjNkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI3QGJsb2Nrc2NvdXQuY29tZAACaWRh12QABG5hbWVtAAAAC1VzZXIgVGVzdDIzZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIzZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIzZAAMd2F0Y2hsaXN0X2lkYdc._6gJnvzjA6VEztgoIdpp7chhmhsdFrJImlcdrp4-pW0; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y3SwObudkicAAHBB +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "message": "OK" +} +``` + +### update_public_tags_request +#### Edit request to add a public tag + +##### Request +* __Method:__ PUT +* __Path:__ /api/account/v1/user/public_tags/132 +* __Request headers:__ +``` +content-type: multipart/mixed; boundary=plug_conn_test +``` +* __Request body:__ +```json +{ + "website": "website2", + "tags": "Tag2;Tag3", + "is_owner": true, + "full_name": "full name2", + "email": "test_user-9@blockscout.com", + "company": "company2", + "addresses": [ + "0x000000000000000000000000000000000000000f", + "0x0000000000000000000000000000000000000010", + "0x0000000000000000000000000000000000000011", + "0x0000000000000000000000000000000000000012", + "0x0000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000014" + ], + "additional_comment": "additional_comment2" +} +``` + +##### Response +* __Status__: 200 +* __Response headers:__ +``` +set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNmQABWVtYWlsbQAAABp0ZXN0X3VzZXItN0BibG9ja3Njb3V0LmNvbWQAAmlkYcZkAARuYW1lbQAAAApVc2VyIFRlc3Q2ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjZkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwNmQADHdhdGNobGlzdF9pZGHG.86gruprPiLE-Nf9xkOzjEcW2wfSnCCPly5fHTwHrF6c; path=/; HttpOnly +content-type: application/json; charset=utf-8 +cache-control: max-age=0, private, must-revalidate +x-request-id: FxF1Y2E03jhU4u4AAGSi +access-control-allow-credentials: true +access-control-allow-origin: * +access-control-expose-headers: +``` +* __Response body:__ +```json +{ + "website": "website2", + "tags": "Tag2;Tag3", + "submission_date": "2022-09-03T21:00:07.000000Z", + "is_owner": true, + "id": 132, + "full_name": "full name2", + "email": "test_user-9@blockscout.com", + "company": "company2", + "addresses": [ + "0x000000000000000000000000000000000000000f", + "0x0000000000000000000000000000000000000010", + "0x0000000000000000000000000000000000000011", + "0x0000000000000000000000000000000000000012", + "0x0000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000014" + ], + "additional_comment": "additional_comment2" +} +``` + diff --git a/apps/block_scout_web/README.md b/apps/block_scout_web/README.md index 5f6d2d67014e..8c1a6223937a 100644 --- a/apps/block_scout_web/README.md +++ b/apps/block_scout_web/README.md @@ -8,21 +8,19 @@ This is a tool for inspecting and analyzing the POA Network blockchain from a we * Elixir 1.9+ * Postgres 10.3 - ## Required Accounts * Github for code storage - ## Setup Instructions ### Development To get BlockScout Web interface up and running locally: - * Setup `../explorer` - * Install Node.js dependencies with `$ cd assets && npm install && cd ..` - * Start Phoenix with `$ mix phx.server` (This can be run from this directory or the project root: the project root is recommended.) +* Setup `../explorer` +* Install Node.js dependencies with `$ cd assets && npm install && cd ..` +* Start Phoenix with `$ mix phx.server` (This can be run from this directory or the project root: the project root is recommended.) Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. @@ -30,14 +28,13 @@ You can also run IEx (Interactive Elixir): `$ iex -S mix phx.server` (This can b ### Testing - * Build the assets: `cd assets && npm run build` - * Format the Elixir code: `mix format` - * Lint the Elixir code: `mix credo --strict` - * Run the dialyzer: `mix dialyzer --halt-exit-status` - * Check the Elixir code for vulnerabilities: `mix sobelow --config` - * Update translations templates and translations and check there are no uncommitted changes: `mix gettext.extract --merge` - * Lint the JavaScript code: `cd assets && npm run eslint` - +* Build the assets: `cd assets && npm run build` +* Format the Elixir code: `mix format` +* Lint the Elixir code: `mix credo --strict` +* Run the dialyzer: `mix dialyzer --halt-exit-status` +* Check the Elixir code for vulnerabilities: `mix sobelow --config` +* Update translations templates and translations and check there are no uncommitted changes: `mix gettext.extract --merge` +* Lint the JavaScript code: `cd assets && npm run eslint` ## Internationalization diff --git a/apps/block_scout_web/assets/css/_images-preload.scss b/apps/block_scout_web/assets/css/_images-preload.scss deleted file mode 100644 index 031cf2088806..000000000000 --- a/apps/block_scout_web/assets/css/_images-preload.scss +++ /dev/null @@ -1,17 +0,0 @@ -body:after { - position:absolute; width:0; height:0; overflow:hidden; z-index:-1; - content: - url(/images/network-selector-icons/callisto-mainnet.svg) - url(/images/network-selector-icons/ethereum-mainnet.svg) - url(/images/network-selector-icons/ethereum-classic.svg) - url(/images/network-selector-icons/goerli-testnet.svg) - url(/images/network-selector-icons/kovan-testnet.svg) - url(/images/network-selector-icons/poa-core.svg) - url(/images/network-selector-icons/poa-sokol.svg) - url(/images/network-selector-icons/rinkeby-testnet.svg) - url(/images/network-selector-icons/rsk-mainnet.svg) - url(/images/network-selector-icons/ropsten-testnet.svg) - url(/images/network-selector-icons/xdai-chain.svg) - url(/images/network-selector-icons/lukso-l14-testnet.svg) - url(/images/network-selector-icons/circle-xusdt.svg) -}; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index 7a5550527fa2..e3468b63580c 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -9,7 +9,7 @@ // Font Awesome $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; -@import "fontawesome"; +@import "~@fortawesome/fontawesome-free/scss/fontawesome"; @import "~@fortawesome/fontawesome-free/scss/brands"; @import "~@fortawesome/fontawesome-free/scss/regular"; @import "~@fortawesome/fontawesome-free/scss/solid"; @@ -124,6 +124,8 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/_dropzone"; @import "components/_search"; @import "components/_ad"; +@import "components/_account"; + // Font Awesome @import "components/_fontawesome_icon"; diff --git a/apps/block_scout_web/assets/css/components/_account.scss b/apps/block_scout_web/assets/css/components/_account.scss new file mode 100644 index 000000000000..8b1fc68a1395 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_account.scss @@ -0,0 +1,143 @@ +div.divider { + height: inherit; + width: 1px; + background: #828ba0; + margin-left: 10px; + margin-right: 10px; +} + +input.profile-item { + margin-left: 10px; + color: #828ba0; + outline: none; +} + +.header-account { + font-size: 18px; +} + +.label-account { + font-size: 1rem; +} + +.white { + color: #fff; +} + +.card-body-account { + max-width: none !important; +} + +.form-input { + display: flex; + margin-bottom: 1rem; +} + +.form-checkbox { + margin-right: 0.5rem; + align-self: center; +} + +.label-checkbox { + font-size: 14px; + margin-bottom: 0; +} + +.o-flow-x { + overflow-x: auto; +} + +.fs-14 { + font-size: 14px; +} + +.acc-link-active { + &:hover, &:focus { + background-color: $primary !important; + color: #fff !important; + } + background-color: $primary; + color: #fff; +} + +.table-watchlist { + margin: 30px; + + @include media-breakpoint-down(md) { + margin: 0; + } +} + +.form-error { + display: block; + font-size: 13px; + line-height: 1.2; + padding-top: 10px; +} + +.navbar-account { + @include media-breakpoint-down(sm) { + max-width: 100%; + flex-grow: 1; + } +} + +.nav-item.account { + @include media-breakpoint-down(sm) { + width: auto; + display: inline-grid; + } +} + +.nav.account { + @include media-breakpoint-down(sm) { + display: inline; + } +} + +li.public-tags-address { + list-style: none; + display: flex; +} + +input.public-tags-address { + flex-basis: 557px; + width: auto; +} + +.add-form-field { + background: none; + border: none; + margin-left: 0.5px; +} + +.remove-form-field { + background: none; + border: none; + + svg { + margin-top: 3px; + } +} + +.multiple-input-fields-container { + padding-inline-start: 0; + display: inline-block; + margin-bottom: 0; +} + +.line-input { + @include media-breakpoint-up(md) { + display: flex; + justify-content: space-between; + + .form-group { + flex-grow: 1; + margin-right: 4rem; + } + } +} + +.mr-4-rem { + margin-right: 4rem; +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_btn_line.scss b/apps/block_scout_web/assets/css/components/_btn_line.scss index b62b345db35c..854ab5b65d25 100644 --- a/apps/block_scout_web/assets/css/components/_btn_line.scss +++ b/apps/block_scout_web/assets/css/components/_btn_line.scss @@ -4,3 +4,13 @@ $btn-line-color: $secondary !default; .btn-line { @include btn-line($btn-line-bg, $btn-line-color); } + +.btn-line-inversed { + @include btn-line($btn-line-color, $btn-line-bg); +} + +.btn-line-inversed:hover { + background-color: $btn-line-color; + color: $btn-line-bg; + text-decoration: none; +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_card.scss b/apps/block_scout_web/assets/css/components/_card.scss index 109b5c47d011..bcbe9dce5821 100644 --- a/apps/block_scout_web/assets/css/components/_card.scss +++ b/apps/block_scout_web/assets/css/components/_card.scss @@ -326,4 +326,22 @@ $card-tab-icon-color-active: #fff !default; .function-output { margin-left: -1rem; +} + +.functions-tabs input[type="radio"] { + display: none; +} + +.card-misc-container { + padding-left: $card-horizontal-padding; + padding-top: $card-horizontal-padding; + + .btn-line-inversed, + .btn-line { + display: inline-flex; + } +} + +.nav-pills .nav-link.active { + background-color: $primary; } \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_errors.scss b/apps/block_scout_web/assets/css/components/_errors.scss index 05a850bc034d..131aa1d55184 100644 --- a/apps/block_scout_web/assets/css/components/_errors.scss +++ b/apps/block_scout_web/assets/css/components/_errors.scss @@ -1,4 +1,18 @@ $error-tablet-breakpoint: 768px; +$error-title-color: #606571; +$error-description-color: #718096; +$error-btn-color: #2B6CB0; +$error-delimiter-color: #D9D9D9; + +// In order to center vertically + +main:has(section.error-container) { + display: flex; +} + +.error-container { + margin: auto; +} // Block Not Found @@ -10,11 +24,12 @@ $error-tablet-breakpoint: 768px; flex-direction: row; align-items: center; justify-content: center; - padding-top: 52px; } + gap: 50px; } .block-not-found-img { + order: -1; margin-bottom: 40px; text-align: center; @media (min-width: $error-tablet-breakpoint) { @@ -27,68 +42,65 @@ $error-tablet-breakpoint: 768px; } } +.block-not-found::before { + align-self: stretch; + border: 1px solid $error-delimiter-color; + border-radius: 10px; + content: ""; +} + .block-not-found-content { text-align: center; @media (min-width: $error-tablet-breakpoint) { text-align: left; - padding-left: 52px; - max-width: 396px; + max-width: 466px; } } .error-title { margin-bottom: 20px !important; + font-weight: 500; + font-size: 48px; + line-height: 58px; + color: $error-title-color; } .error-descr { display: block; - font-size: 14px; - color: #a3a9b5; - line-height: 1.714; + font-size: 16px; + color: $error-description-color; + line-height: 30px; margin-bottom: 22px; word-wrap: break-word; } .error-btn { + @include btn-line($btn-line-bg, $error-btn-color, 16px); + border-width: 2px; background: transparent; display: inline-flex !important; + border-radius: 8px; + font-weight: 600; + line-height: 24px; +} + +.dark-theme-applied .error-btn { + color: $error-btn-color; } // TX Not Found .tx-nf { display: flex; - align-items: center; + align-items: flex-start; justify-content: center; flex-direction: column; - padding-bottom: 50px; - @media (min-width: $error-tablet-breakpoint) { - flex-direction: row; - padding-top: 52px; - } -} + margin: auto; + padding-bottom: 3rem; + max-width: 827px; + gap: 12px; -.tx-nf-content { - margin-top: 52px; - max-width: 700px; - @media (min-width: $error-tablet-breakpoint) { - margin-left: 90px; - margin-top: 0; - } -} - -.tx-nf-blocks { - margin-bottom: 40px; - margin-top: 40px; -} - -.tx-nf-block { - background-color: #fff; - box-shadow: 0px 0px 30px 0px rgba(202, 199, 226, 0.5); - padding: 17px 40px 17px 20px; - position: relative; - border-radius: 6px; - @media (min-width: $error-tablet-breakpoint) { - width: calc(50% - 20px); + .error-title { + margin-bottom: 0 !important; } } @@ -96,42 +108,4 @@ $error-tablet-breakpoint: 768px; .error-descr { margin-bottom: 0; } - & + .tx-nf-block { - margin-top: 30px; - @media (min-width: $error-tablet-breakpoint) { - margin-top: 0; - } - } } - -.tx-nf-blocks-row { - display: flex; - justify-content: space-between; - flex-direction: column; - @media (min-width: $error-tablet-breakpoint) { - flex-direction: row; - } - & + .tx-nf-blocks-row { - margin-top: 30px; - @media (min-width: $error-tablet-breakpoint) { - margin-top: 40px; - } - } -} - -.tx-nf-number { - display: inline-block; - position: absolute; - top: -15px; - left: -15px; - width: 30px; - height: 30px; - border-radius: 15px; - background-color: $secondary; - box-shadow: 0px 3px 5px 0px rgba($secondary, 0.25); - color: #fff; - font-weight: 700; - font-size: 14px; - text-align: center; - line-height: 32px; -} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_label.scss b/apps/block_scout_web/assets/css/components/_label.scss index ecde607546b5..43599ecee607 100644 --- a/apps/block_scout_web/assets/css/components/_label.scss +++ b/apps/block_scout_web/assets/css/components/_label.scss @@ -95,4 +95,10 @@ border-top: .8rem solid transparent; border-bottom: .8rem solid transparent; border-left: .8rem solid rgba(245, 246, 250, 1); +} + +.testnet-label { + background-color: transparent; + border: 1px solid #828ba0; + color: #828ba0; } \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss index 39b2149c1115..63f08e1a2d4e 100644 --- a/apps/block_scout_web/assets/css/components/_navbar.scss +++ b/apps/block_scout_web/assets/css/components/_navbar.scss @@ -10,8 +10,8 @@ $header-textfield-text-color: $header-links-color !default; $header-textfield-background-color: #f5f6fa !default; $header-textfield-magnifier-color: $header-links-color !default; $header-link-horizontal-padding: 0.71rem; -$navbar-logo-height: 28px !default; -$navbar-logo-width: auto !default; +$navbar-logo-height: auto !default; +$navbar-logo-width: 100% !default; .navbar.navbar-primary { background-color: $header-background-color; @@ -23,6 +23,11 @@ $navbar-logo-width: auto !default; } .navbar-nav { + + .nav-submenu { + padding: 10px 20px; + color: $header-links-color; + } .nav-link { outline: none; @@ -242,7 +247,7 @@ $navbar-logo-width: auto !default; } .navbar-logo { - height: $navbar-logo-height; + max-height: $navbar-logo-height; width: $navbar-logo-width; } diff --git a/apps/block_scout_web/assets/css/components/_network-selector.scss b/apps/block_scout_web/assets/css/components/_network-selector.scss deleted file mode 100644 index e3ffea33de80..000000000000 --- a/apps/block_scout_web/assets/css/components/_network-selector.scss +++ /dev/null @@ -1,371 +0,0 @@ -$network-selector-overlay-background: $modal-overlay-color !default; -$network-selector-close-color: $primary !default; -$network-selector-horizontal-padding: 28px; -$network-selector-horizontal-mobile-padding: 14px; -$btn-network-selector-load-more-background: #fff !default; -$btn-network-selector-load-more-color: $primary !default; -$network-selector-search-input-color: #a3a9b5 !default; -$network-selector-tab-active-border-color: $primary !default; -$network-selector-item-icon-dimensions: 30px !default; - -.network-selector-visible { - bottom: 0; - left: 0; - position: fixed; - right: 0; - top: 0; -} - -.network-selector-overlay { - background-color: rgba($network-selector-overlay-background, 0.9); - bottom: 0; - display: none; - left: 0; - position: fixed; - right: 0; - top: 0; - z-index: 123; -} - -.network-selector-overlay-close { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.network-selector-wrapper { - display: flex; - height: 100%; - width: 100%; -} - -.network-selector { - background-color: #fff; - display: flex; - flex-direction: column; - flex-grow: 1; - flex-shrink: 1; - margin-left: auto; - max-width: 398px; - min-width: 0; - padding-top: 28px; - position: relative; - transition: right 0.25s ease-out; - z-index: 2; -} - -.network-selector-close { - flex-shrink: 0; - padding: 0 $network-selector-horizontal-padding; - margin: 0 0 8px; - - svg { - cursor: pointer; - display: block; - margin-left: auto; - } - - path { - fill: $network-selector-close-color; - } -} - -.network-selector-text-container { - flex-shrink: 0; - margin: 0 0 15px; - padding: 0 $network-selector-horizontal-padding; -} - -.network-selector-title { - color: #333; - font-size: 18px; - font-weight: normal; - line-height: 1.2; - margin: 0 0 10px; - padding: 0; -} - -.network-selector-text { - color: #a3a9b5; - font-size: 12px; - font-weight: normal; - line-height: 1.67; - margin: 0; - padding: 0; -} - -.network-selector-search-container { - align-items: center; - background-color: #f5f6fa; - display: flex; - flex-shrink: 0; - height: 62px; - margin: 0; - padding: 0 $network-selector-horizontal-padding; - - path { - flex-grow: 0; - flex-shrink: 0; - fill: $network-selector-search-input-color; - } -} - -.network-selector-search-input { - background-color: transparent; - border-color: transparent; - color: #333; - flex-grow: 1; - font-size: 14px; - font-weight: 600; - height: 100%; - outline: none; - padding: 0 20px 0 10px; - - &[placeholder]{ - color: $network-selector-search-input-color !important; - } - &::-webkit-input-placeholder { /* Chrome/Opera/Safari */ - color: $network-selector-search-input-color !important; - } - &::-moz-placeholder { /* Firefox 19+ */ - color: $network-selector-search-input-color !important; - } - &:-ms-input-placeholder { /* IE 10+ */ - color: $network-selector-search-input-color !important; - } - &:-moz-placeholder { /* Firefox 18- */ - color: $network-selector-search-input-color !important; - } -} - -.network-selector-tabs-container { - border-bottom: 1px solid $base-border-color; - display: flex; - flex-shrink: 0; - margin: 0 $network-selector-horizontal-mobile-padding; - @media (min-width: 375px) { - margin: 0 $network-selector-horizontal-padding; - } -} - -.network-selector-tab { - color: #a3a9b5; - cursor: pointer; - flex-shrink: 1; - font-size: 14px; - font-weight: 600; - line-height: 1.2; - min-width: 0; - padding: 20px 18px 15px; - position: relative; - text-align: center; - user-select: none; - white-space: nowrap; - - &:hover { - color: #333; - } - - &.active { - color: #333; - cursor: default; - - &::after { - background-color: $network-selector-tab-active-border-color; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - bottom: 0; - content: ""; - height: 4px; - left: 0; - position: absolute; - right: 0; - } - } -} - -.network-selector-tab-content { - display: none; - - &.active { - display: block; - } -} - -.network-selector-item { - border-bottom: 1px solid $base-border-color; - display: flex; - position: relative; - - .radio { - cursor: pointer; - margin: 0 15px 0 0; - - input[type="radio"] { - cursor: pointer; - } - } - - .radio-icon { - margin: 0; - } - - &:last-child { - border-bottom: none; - } -} - -.network-selector-item-url { - align-items: center; - cursor: pointer; - display: flex; - flex-grow: 1; - margin: 0; - padding: 20px 0; - - &:hover { - .network-selector-item-type { - color: #333; - } - } -} - -.network-selector-item-icon { - background-color: #dfdfdf; - background-position: 50% 50%; - background-repeat: no-repeat; - background-size: contain; - border-radius: 50%; - flex-grow: 0; - flex-shrink: 0; - height: $network-selector-item-icon-dimensions; - margin: 0 15px 0 0; - width: $network-selector-item-icon-dimensions; - - &-callisto-mainnet { - background-image: url(/images/network-selector-icons/callisto-mainnet.svg) - } - &-ethereum-mainnet { - background-image: url(/images/network-selector-icons/ethereum-mainnet.svg) - } - &-ethereum-classic { - background-image: url(/images/network-selector-icons/ethereum-classic.svg) - } - &-goerli-testnet { - background-image: url(/images/network-selector-icons/goerli-testnet.svg) - } - &-kovan-testnet { - background-image: url(/images/network-selector-icons/kovan-testnet.svg) - } - &-poa-core { - background-image: url(/images/network-selector-icons/poa-core.svg) - } - &-poa-sokol { - background-image: url(/images/network-selector-icons/poa-sokol.svg) - } - &-rinkeby-testnet { - background-image: url(/images/network-selector-icons/rinkeby-testnet.svg) - } - &-rsk-mainnet { - background-image: url(/images/network-selector-icons/rsk-mainnet.svg) - } - &-ropsten-testnet { - background-image: url(/images/network-selector-icons/ropsten-testnet.svg) - } - &-xdai-chain { - background-image: url(/images/network-selector-icons/xdai-chain.svg) - } - &-lukso-l14-testnet { - background-image: url(/images/network-selector-icons/lukso-l14-testnet.svg) - } - &-xusdt-chain { - background-image: url(/images/network-selector-icons/circle-xusdt.svg) - } -} - -.network-selector-item-title { - color: #333; - flex-grow: 1; - font-size: 14px; - font-weight: normal; - line-height: 1.2; - overflow: hidden; - text-align: left; - text-overflow: ellipsis; - user-select: none; - white-space: nowrap; -} - -.network-selector-item-type { - color: #a3a9b5; - flex-shrink: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.2; - padding-left: 10px; - text-align: right; - user-select: none; - white-space: nowrap; -} - -.network-selector-item-content { - align-items: center; - display: flex; - flex-grow: 1; -} - -.network-selector-networks-container { - flex-grow: 1; - flex-shrink: 1; - min-height: 100px; - overflow: auto; - padding: 0 $network-selector-horizontal-mobile-padding; - @media (min-width: 375px) { - padding: 0 $network-selector-horizontal-padding; - } -} - -.network-selector-item-favorite { - align-items: center; - cursor: pointer; - display: flex; - flex-grow: 1; - flex-shrink: 0; - margin: 0; - max-width: 36px; - padding-left: 20px; - position: relative; - - input[type="checkbox"] { - cursor: pointer; - height: 100%; - opacity: 0; - position: absolute; - width: 100%; - z-index: 5; - - &:checked + svg { - position: relative; - z-index: 1; - - path { - fill: #ffb20d; - } - } - } - - &:hover { - path { - fill: rgba(#ffb20d, 0.4); - } - } -} - -.network-selector-tab-content-empty { - font-size: 16px; - font-weight: 600; - padding: 40px; - text-align: center; -} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_token-balance-dropdown.scss b/apps/block_scout_web/assets/css/components/_token-balance-dropdown.scss index 87b8992bcfea..83b9746640fa 100644 --- a/apps/block_scout_web/assets/css/components/_token-balance-dropdown.scss +++ b/apps/block_scout_web/assets/css/components/_token-balance-dropdown.scss @@ -102,7 +102,16 @@ margin-left: 5px; } +.ml-20px { + margin-left: 20px !important; +} + .dropdown-row { padding-left: 5px; padding-right: 5px; +} + +.dropdown-amount { + font-size: 12px; + opacity: .65; } \ 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 fcd8b663f064..d431ad834cdb 100644 --- a/apps/block_scout_web/assets/css/theme/_dark-theme.scss +++ b/apps/block_scout_web/assets/css/theme/_dark-theme.scss @@ -43,6 +43,9 @@ $dark-stakes-banned-background: #3e314c; background-color: $dark-light-bg; } } + .nav-submenu { + color: $labels-dark; + } } .navbar-brand .navbar-logo { filter: brightness(0) invert(1); @@ -439,62 +442,6 @@ $dark-stakes-banned-background: #3e314c; border-left-color: rgba(#fff, 0.5) !important; } - //network select - .network-selector-overlay { - background-color: rgba($dark-bg, .9); - } - .network-selector { - background-color: $dark-light-bg; - } - .network-selector-title { - color: #fff; - } - .network-selector-text { - color: $labels-dark; - } - .network-selector-close path { - fill: #fff; - } - .network-selector-search-container { - background-color: $dark-light; - } - .network-selector-search-container path { - fill: $labels-dark; - } - .network-selector-search-input { - color: #fff !important; - &::placeholder { - color: $labels-dark; - } - } - .network-selector-tab { - color: $labels-dark; - &:hover, &.active { - color: #fff; - } - &.active { - &:after { - background-color: $dark-primary; - } - } - } - .network-selector-item, - .network-selector-tabs-container { - border-bottom-color: darken($labels-dark, 30); - } - .network-selector-item-title { - color: #fff; - } - .network-selector-item-type { - color: $labels-dark; - } - .radio .radio-icon { - border-color: $labels-dark - } - .network-selector-item-url:hover .network-selector-item-type { - color: #fff; - } - //coin dropdown .token-balance-dropdown.dropdown-menu { border-color: $dark-light !important; diff --git a/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss b/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss index 3d036aec6984..3ae71f538010 100644 --- a/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss +++ b/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss @@ -285,59 +285,6 @@ $c-dark-text-color: #8a8dba; background-color: $c-primary; } - //network select - .network-selector-overlay { - background-color: rgba($c-primary, .9); - } - .network-selector { - background-color: $c-primary; - } - .network-selector-text { - color: $text2; - } - .network-selector-close path { - fill: #fff; - } - .network-selector-search-container { - background-color: $c-primary; - } - .network-selector-search-container path { - fill: $text2; - } - .network-selector-search-input { - color: #fff !important; - &::placeholder { - color: $text2; - } - } - .network-selector-tab { - color: $text2; - &:hover, &.active { - color: #fff; - } - &.active { - &:after { - background-color: $c-primary; - } - } - } - .network-selector-item, - .network-selector-tabs-container { - border-bottom-color: darken($text2, 30); - } - .network-selector-item-title { - color: #fff; - } - .network-selector-item-type { - color: $text2; - } - .radio .radio-icon { - border-color: $text2; - } - .network-selector-item-url:hover .network-selector-item-type { - color: #fff; - } - //coin dropdown .token-balance-dropdown[aria-labelledby="dropdown-tokens"] .dropdown-items .dropdown-item:hover { color: #fff !important; @@ -764,6 +711,7 @@ $c-dark-text-color: #8a8dba; color: $c-primary; } -.circles-theme-applied.dark-theme-applied a:hover, a:focus { +.circles-theme-applied.dark-theme-applied a:hover, +.circles-theme-applied.dark-theme-applied a:focus { color: $c-primary; } \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss b/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss index 415f79d333a7..e008052008c8 100644 --- a/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss +++ b/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss @@ -438,62 +438,6 @@ $dark-primary-alternate: $dark-primary; background-color: $dark-primary; } - //network select - .network-selector-overlay { - background-color: rgba($dark-bg, .9); - } - .network-selector { - background-color: $dark-light-bg; - } - .network-selector-title { - color: #fff; - } - .network-selector-text { - color: $labels-dark; - } - .network-selector-close path { - fill: #fff; - } - .network-selector-search-container { - background-color: $dark-light; - } - .network-selector-search-container path { - fill: $labels-dark; - } - .network-selector-search-input { - color: #fff !important; - &::placeholder { - color: $labels-dark; - } - } - .network-selector-tab { - color: $labels-dark; - &:hover, &.active { - color: #fff; - } - &.active { - &:after { - background-color: $dark-primary; - } - } - } - .network-selector-item, - .network-selector-tabs-container { - border-bottom-color: darken($labels-dark, 30); - } - .network-selector-item-title { - color: #fff; - } - .network-selector-item-type { - color: $labels-dark; - } - .radio .radio-icon { - border-color: $labels-dark - } - .network-selector-item-url:hover .network-selector-item-type { - color: #fff; - } - //coin dropdown .token-balance-dropdown.dropdown-menu { border-color: $dark-light !important; diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index df99725add04..6366564cf8a1 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -35,3 +35,6 @@ import './lib/tooltip' import './lib/modals' import './lib/card_tabs' import './lib/ad' + +import swal from 'sweetalert2' +window.Swal = swal diff --git a/apps/block_scout_web/assets/js/lib/ad.js b/apps/block_scout_web/assets/js/lib/ad.js index 91ee45e80789..69f0ce3577e5 100644 --- a/apps/block_scout_web/assets/js/lib/ad.js +++ b/apps/block_scout_web/assets/js/lib/ad.js @@ -9,7 +9,7 @@ function countImpressions (impressionUrl) { function showAd () { const domainName = window.location.hostname - if (domainName.endsWith('blockscout.com')) { + if (domainName === 'blockscout.com' || 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') diff --git a/apps/block_scout_web/assets/js/lib/add_chain_to_mm.js b/apps/block_scout_web/assets/js/lib/add_chain_to_mm.js index f5970763488c..d10820f1da63 100644 --- a/apps/block_scout_web/assets/js/lib/add_chain_to_mm.js +++ b/apps/block_scout_web/assets/js/lib/add_chain_to_mm.js @@ -2,29 +2,33 @@ import 'bootstrap' export async function addChainToMM ({ btn }) { try { - const chainID = await window.ethereum.request({ method: 'eth_chainId' }) - const chainIDFromEnvVar = parseInt(process.env.CHAIN_ID) - const chainIDHex = chainIDFromEnvVar && `0x${chainIDFromEnvVar.toString(16)}` + const chainIDFromWallet = await window.ethereum.request({ method: 'eth_chainId' }) + const chainIDFromInstance = getChainIdHex() + + const coinName = document.getElementById('js-coin-name').value + const subNetwork = document.getElementById('js-subnetwork').value + const jsonRPC = document.getElementById('js-json-rpc').value + const blockscoutURL = location.protocol + '//' + location.host + process.env.NETWORK_PATH - if (chainID !== chainIDHex) { + if (chainIDFromWallet !== chainIDFromInstance) { await window.ethereum.request({ method: 'wallet_addEthereumChain', params: [{ - chainId: chainIDHex, - chainName: process.env.SUBNETWORK, + chainId: chainIDFromInstance, + chainName: subNetwork, nativeCurrency: { - name: process.env.COIN_NAME, - symbol: process.env.COIN_NAME, + name: coinName, + symbol: coinName, decimals: 18 }, - rpcUrls: [process.env.JSON_RPC], + rpcUrls: [jsonRPC], blockExplorerUrls: [blockscoutURL] }] }) } else { btn.tooltip('dispose') btn.tooltip({ - title: `You're already connected to ${process.env.SUBNETWORK}`, + title: `You're already connected to ${subNetwork}`, trigger: 'click', placement: 'bottom' }).tooltip('show') @@ -37,3 +41,9 @@ export async function addChainToMM ({ btn }) { console.error(error) } } + +function getChainIdHex () { + const chainIDFromDOM = document.getElementById('js-chain-id').value + const chainIDFromInstance = parseInt(chainIDFromDOM) + return chainIDFromInstance && `0x${chainIDFromInstance.toString(16)}` +} diff --git a/apps/block_scout_web/assets/js/lib/async_listing_load.js b/apps/block_scout_web/assets/js/lib/async_listing_load.js index 54aee37e0301..785b1f4e2fa6 100644 --- a/apps/block_scout_web/assets/js/lib/async_listing_load.js +++ b/apps/block_scout_web/assets/js/lib/async_listing_load.js @@ -305,7 +305,7 @@ export const elements = { */ export function createAsyncLoadStore (reducer, initialState, itemKey) { const state = merge(asyncInitialState, initialState) - const store = createStore(reduceReducers(asyncReducer, reducer, state)) + const store = createStore(reduceReducers(state, asyncReducer, reducer)) if (typeof itemKey !== 'undefined') { store.dispatch({ diff --git a/apps/block_scout_web/assets/js/lib/datepicker.js b/apps/block_scout_web/assets/js/lib/csv_download.js similarity index 62% rename from apps/block_scout_web/assets/js/lib/datepicker.js rename to apps/block_scout_web/assets/js/lib/csv_download.js index b9dabd50d501..2fe63c14380b 100644 --- a/apps/block_scout_web/assets/js/lib/datepicker.js +++ b/apps/block_scout_web/assets/js/lib/csv_download.js @@ -1,6 +1,7 @@ import * as Pikaday from 'pikaday' import moment from 'moment' import $ from 'jquery' +import Cookies from 'js-cookie' const DATE_FORMAT = 'YYYY-MM-DD' @@ -27,38 +28,33 @@ const _instance2 = new Pikaday({ }) $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 + const recaptchaResponse = grecaptcha.getResponse() + if (recaptchaResponse) { + $button.addClass('spinner') + $button.prop('disabled', true) + const downloadUrl = `${$button.data('link')}&recaptcha_response=${recaptchaResponse}` + + $('body').append($('')) + $('#csv-iframe').attr('src', downloadUrl) + + const interval = setInterval(handleCSVDownloaded, 1000) + setTimeout(resetDownload, 60000) + + function handleCSVDownloaded () { + if (Cookies.get('csv-downloaded') === 'true') { + resetDownload() } - }) - .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 resetDownload () { + $button.removeClass('spinner') + $button.prop('disabled', false) + clearInterval(interval) + Cookies.remove('csv-downloaded') + // eslint-disable-next-line + grecaptcha.reset() + } } }) diff --git a/apps/block_scout_web/assets/js/lib/history_chart.js b/apps/block_scout_web/assets/js/lib/history_chart.js index c7c94cbe8463..674c4ef32eec 100644 --- a/apps/block_scout_web/assets/js/lib/history_chart.js +++ b/apps/block_scout_web/assets/js/lib/history_chart.js @@ -1,4 +1,5 @@ import $ from 'jquery' +import Cookies from 'js-cookie' import { Chart, LineController, LineElement, PointElement, LinearScale, TimeScale, Title, Tooltip } from 'chart.js' import 'chartjs-adapter-luxon' import humps from 'humps' @@ -17,7 +18,7 @@ const grid = { } function getTxChartColor () { - if (localStorage.getItem('current-color-mode') === 'dark') { + if (Cookies.get('chakra-ui-color-mode') === 'dark') { return sassVariables.dashboardLineColorTransactionsDarkTheme } else { return sassVariables.dashboardLineColorTransactions @@ -25,7 +26,7 @@ function getTxChartColor () { } function getPriceChartColor () { - if (localStorage.getItem('current-color-mode') === 'dark') { + if (Cookies.get('chakra-ui-color-mode') === 'dark') { return sassVariables.dashboardLineColorPriceDarkTheme } else { return sassVariables.dashboardLineColorPrice @@ -33,7 +34,7 @@ function getPriceChartColor () { } function getMarketCapChartColor () { - if (localStorage.getItem('current-color-mode') === 'dark') { + if (Cookies.get('chakra-ui-color-mode') === 'dark') { return sassVariables.dashboardLineColorMarketDarkTheme } else { return sassVariables.dashboardLineColorMarket diff --git a/apps/block_scout_web/assets/js/lib/indexing.js b/apps/block_scout_web/assets/js/lib/indexing.js index be410da5b810..e406d35ce1e8 100644 --- a/apps/block_scout_web/assets/js/lib/indexing.js +++ b/apps/block_scout_web/assets/js/lib/indexing.js @@ -3,23 +3,48 @@ import humps from 'humps' import numeral from 'numeral' import socket from '../socket' -function tryUpdateIndexedStatus (el, indexedRatio = el.dataset.indexedRatio, indexingFinished = false) { +function tryUpdateIndexedStatus (el, indexedRatioBlocks = el.dataset.indexedRatioBlocks, indexedRatio = el.dataset.indexedRatio, indexingFinished = false) { if (indexingFinished) return $("[data-selector='indexed-status']").remove() - const blocksPercentComplete = numeral(indexedRatio).format('0%') + const indexedRatioFloat = parseFloat(indexedRatio) + const indexedRatioBlocksFloat = parseFloat(indexedRatioBlocks) + + if (!isNaN(indexedRatioBlocksFloat)) { + el.dataset.indexedRatioBlocks = indexedRatioBlocks + } else if (!isNaN(indexedRatioFloat)) { + el.dataset.indexedRatio = indexedRatio + } + + const blocksPercentComplete = numeral(el.dataset.indexedRatioBlocks).format('0%') let indexedText if (blocksPercentComplete === '100%') { - indexedText = window.localized['Indexing Tokens'] + const intTxsPercentComplete = numeral(el.dataset.indexedRatio).format('0%') + indexedText = `${intTxsPercentComplete} ${window.localized['Blocks With Internal Transactions Indexed']}` } else { indexedText = `${blocksPercentComplete} ${window.localized['Blocks Indexed']}` } - if (indexedText !== el.innerHTML) el.innerHTML = indexedText + + if (indexedText !== el.innerHTML) { + el.innerHTML = indexedText + } } -export function updateIndexStatus (msg = {}) { - $('[data-indexed-ratio]').each((i, el) => tryUpdateIndexedStatus(el, msg.ratio, msg.finished)) +export function updateIndexStatus (msg = {}, type) { + $('[data-indexed-ratio]').each((i, el) => { + if (type === 'blocks') { + tryUpdateIndexedStatus(el, msg.ratio, null, msg.finished) + } else if (type === 'internal_transactions') { + tryUpdateIndexedStatus(el, null, msg.ratio, msg.finished) + } else { + tryUpdateIndexedStatus(el, null, null, msg.finished) + } + }) } updateIndexStatus() -const indexingChannel = socket.channel('blocks:indexing') -indexingChannel.join() -indexingChannel.on('index_status', (msg) => updateIndexStatus(humps.camelizeKeys(msg))) +const IndexingChannelBlocks = socket.channel('blocks:indexing') +IndexingChannelBlocks.join() +IndexingChannelBlocks.on('index_status', (msg) => updateIndexStatus(humps.camelizeKeys(msg), 'blocks')) + +const indexingChannelInternalTransactions = socket.channel('blocks:indexing_internal_transactions') +indexingChannelInternalTransactions.join() +indexingChannelInternalTransactions.on('index_status', (msg) => updateIndexStatus(humps.camelizeKeys(msg), 'internal_transactions')) diff --git a/apps/block_scout_web/assets/js/lib/network_selector.js b/apps/block_scout_web/assets/js/lib/network_selector.js deleted file mode 100644 index 48648753bdc7..000000000000 --- a/apps/block_scout_web/assets/js/lib/network_selector.js +++ /dev/null @@ -1,77 +0,0 @@ -import $ from 'jquery' - -$(function () { - const mainBody = $('body') - const showNetworkSelector = $('.js-show-network-selector') - const hideNetworkSelector = $('.js-network-selector-close') - const hideNetworkSelectorOverlay = $('.js-network-selector-overlay-close') - const networkSelector = $('.js-network-selector') - const networkSelectorOverlay = $('.js-network-selector-overlay') - const networkSelectorTab = $('.js-network-selector-tab') - const networkSelectorTabContent = $('.js-network-selector-tab-content') - const networkSelectorItemURL = $('.js-network-selector-item-url') - const FADE_IN_DELAY = 250 - - showNetworkSelector.on('click', (e) => { - e.preventDefault() - openNetworkSelector() - }) - - hideNetworkSelector.on('click', (e) => { - e.preventDefault() - closeNetworkSelector() - }) - - hideNetworkSelectorOverlay.on('click', (e) => { - e.preventDefault() - closeNetworkSelector() - }) - - networkSelectorTab.on('click', function (e) { - e.preventDefault() - setNetworkTab($(this)) - }) - - networkSelectorItemURL.on('click', function (e) { - window.location = $(this).attr('network-selector-item-url') - }) - - const setNetworkTab = (currentTab) => { - if (currentTab.hasClass('active')) return - - networkSelectorTab.removeClass('active') - currentTab.addClass('active') - networkSelectorTabContent.removeClass('active') - $(`[network-selector-tab="${currentTab.attr('network-selector-tab-filter')}"]`).addClass('active') - } - - const openNetworkSelector = () => { - mainBody.addClass('network-selector-visible') - networkSelectorOverlay.fadeIn(FADE_IN_DELAY) - setNetworkSelectorVisiblePosition() - } - - const closeNetworkSelector = () => { - mainBody.removeClass('network-selector-visible') - networkSelectorOverlay.fadeOut(FADE_IN_DELAY) - setNetworkSelectorHiddenPosition() - } - - const getNetworkSelectorWidth = () => { - return parseInt(networkSelector.css('width')) || parseInt(networkSelector.css('max-width')) - } - - const setNetworkSelectorHiddenPosition = () => { - return networkSelector.css({ right: `-${getNetworkSelectorWidth()}px` }) - } - - const setNetworkSelectorVisiblePosition = () => { - return networkSelector.css({ right: '0' }) - } - - const init = () => { - setNetworkSelectorHiddenPosition() - } - - init() -}) diff --git a/apps/block_scout_web/assets/js/lib/public_tags_request_form.js b/apps/block_scout_web/assets/js/lib/public_tags_request_form.js new file mode 100644 index 000000000000..a1f88a3f1197 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/public_tags_request_form.js @@ -0,0 +1,43 @@ +import $ from 'jquery' + +const $removeButton = $('.remove-form-field')[0] +const $container = $('#' + $removeButton.dataset.container) +const index = parseInt($container[0].dataset.index) + +if (index <= 1) { + $('.remove-form-field').hide() +} + +$('.add-form-field').on('click', (event) => { + event.preventDefault() + console.log(event) + const $container = $('#' + event.currentTarget.dataset.container) + const index = parseInt($container[0].dataset.index) + if (index < 10) { + $container.append($.parseHTML(event.currentTarget.dataset.prototype)) + $container[0].dataset.index = index + 1 + } + if (index >= 9) { + $('.add-form-field').hide() + } + if (index <= 1) { + $('.remove-form-field').show() + } +}) + +$('[data-multiple-input-field-container]').on('click', '.remove-form-field', (event) => { + event.preventDefault() + console.log(event) + const $container = $('#' + event.currentTarget.dataset.container) + const index = parseInt($container[0].dataset.index) + if (index > 1) { + $container[0].dataset.index = index - 1 + event.currentTarget.parentElement.remove() + } + if (index >= 10) { + $('.add-form-field').show() + } + if (index <= 2) { + $('.remove-form-field').hide() + } +}) diff --git a/apps/block_scout_web/assets/js/lib/random_access_pagination.js b/apps/block_scout_web/assets/js/lib/random_access_pagination.js index 6ef20ce6e87b..bc36d26461e6 100644 --- a/apps/block_scout_web/assets/js/lib/random_access_pagination.js +++ b/apps/block_scout_web/assets/js/lib/random_access_pagination.js @@ -227,7 +227,7 @@ export const elements = { */ export function createAsyncLoadStore (reducer, initialState, itemKey) { const state = merge(asyncInitialState, initialState) - const store = createStore(reduceReducers(asyncReducer, reducer, state)) + const store = createStore(reduceReducers(state, asyncReducer, reducer)) if (typeof itemKey !== 'undefined') { store.dispatch({ diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/connect.js b/apps/block_scout_web/assets/js/lib/smart_contract/connect.js index 8d662e863b45..ca896ef1957d 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/connect.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/connect.js @@ -4,9 +4,11 @@ import WalletConnectProvider from '@walletconnect/web3-provider' import { compareChainIDs, formatError, showConnectElements, showConnectedToElements } from './common_helpers' import { openWarningModal } from '../modals' -const instanceChainId = process.env.CHAIN_ID ? parseInt(`${process.env.CHAIN_ID}`, 10) : 100 +const instanceChainIdStr = document.getElementById('js-chain-id').value +const instanceChainId = parseInt(instanceChainIdStr, 10) const walletConnectOptions = { rpc: {}, chainId: instanceChainId } -walletConnectOptions.rpc[instanceChainId] = process.env.JSON_RPC ? process.env.JSON_RPC : 'https://dai.poa.network' +const jsonRPC = document.getElementById('js-json-rpc').value +walletConnectOptions.rpc[instanceChainId] = jsonRPC // Chosen wallet provider given by the dialog window let provider 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 bf23ea03fbcd..6230972ba7d1 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 @@ -4,7 +4,7 @@ import { queryMethod, callMethod } from './interact' import { walletEnabled, connectToWallet, disconnectWallet, web3ModalInit } from './connect.js' import '../../pages/address' -const loadFunctions = (element) => { +const loadFunctions = (element, isCustomABI) => { const $element = $(element) const url = $element.data('url') const hash = $element.data('hash') @@ -13,7 +13,7 @@ const loadFunctions = (element) => { $.get( url, - { hash, type, action }, + { hash, type, action, is_custom_abi: isCustomABI }, response => $element.html(response) ) .done(function () { @@ -21,7 +21,9 @@ const loadFunctions = (element) => { document.querySelector(disconnectSelector) && document.querySelector(disconnectSelector).addEventListener('click', disconnectWallet) web3ModalInit(connectToWallet) - $('[data-function]').each((_, element) => { + const selector = isCustomABI ? '[data-function-custom]' : '[data-function]' + + $(selector).each((_, element) => { readWriteFunction(element) }) @@ -79,9 +81,10 @@ const readWriteFunction = (element) => { return } const type = $('[data-smart-contract-functions]').data('type') + const isCustomABI = $form.data('custom-abi') walletEnabled() - .then((isWalletEnabled) => queryMethod(isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer)) + .then((isWalletEnabled) => queryMethod(isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer, isCustomABI)) } else if (action === 'write') { const explorerChainId = $form.data('chainId') walletEnabled() @@ -93,5 +96,11 @@ const readWriteFunction = (element) => { const container = $('[data-smart-contract-functions]') if (container.length) { - loadFunctions(container) + loadFunctions(container, false) +} + +const customABIContainer = $('[data-smart-contract-functions-custom]') + +if (customABIContainer.length) { + loadFunctions(customABIContainer, true) } diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/interact.js b/apps/block_scout_web/assets/js/lib/smart_contract/interact.js index 960cf4ef04d0..eaacbfe814c4 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/interact.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/interact.js @@ -3,11 +3,12 @@ import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessag import { compareChainIDs, formatError, formatTitleAndError, getContractABI, getCurrentAccountPromise, getMethodInputs, prepareMethodArgs } from './common_helpers' import BigNumber from 'bignumber.js' -export const queryMethod = (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer) => { +export const queryMethod = (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer, isCustomABI) => { const data = { function_name: functionName, method_id: $methodId.val(), - type + type, + is_custom_abi: isCustomABI } data.args_count = args.length @@ -62,7 +63,7 @@ export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $f openErrorModal(titleAndError.title.length ? titleAndError.title : `Error in sending transaction for method "${functionName}"`, message, false) }) .on('transactionHash', function (txHash) { - onTransactionHash(txHash, $element, functionName) + onTransactionHash(txHash, functionName) }) } else { const txParams = { @@ -75,7 +76,7 @@ export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $f params: [txParams] }) .then(function (txHash) { - onTransactionHash(txHash, $element, functionName) + onTransactionHash(txHash, functionName) }) .catch(function (error) { openErrorModal('Error in sending transaction for fallback method', formatError(error), false) @@ -88,8 +89,8 @@ export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $f }) } -function onTransactionHash (txHash, $element, functionName) { - openModalWithMessage($element.find('#pending-contract-write'), true, txHash) +function onTransactionHash (txHash, functionName) { + openModalWithMessage($('#pending-contract-write'), true, txHash) const getTxReceipt = (txHash) => { window.ethereum.request({ method: 'eth_getTransactionReceipt', diff --git a/apps/block_scout_web/assets/js/pages/account/delete_item_handler.js b/apps/block_scout_web/assets/js/pages/account/delete_item_handler.js new file mode 100644 index 000000000000..0e357e037e03 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/account/delete_item_handler.js @@ -0,0 +1,19 @@ +import $ from 'jquery' + +$('[data-delete-item]').on('click', (event) => { + event.preventDefault() + + if (confirm('Are you sure you want to delete item?')) { + $(event.currentTarget.parentElement).find('form').trigger('submit') + } +}) + +$('[data-delete-request]').on('click', (event) => { + event.preventDefault() + + const result = prompt('Public tags: "' + event.currentTarget.dataset.tags.replace(';', '" and "') + '" will be removed.\nWhy do you want to remove tags?') + if (result) { + $(event.currentTarget.parentElement).find('[name="remove_reason"]').val(result) + $(event.currentTarget.parentElement).find('form').trigger('submit') + } +}) diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 6c759f4038ea..47f13897202b 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -199,14 +199,14 @@ const elements = { }, '[data-selector="current-coin-balance"]': { render ($el, state, oldState) { - if (!state.newBlockNumber || state.newBlockNumber > oldState.newBlockNumber) return + if (!state.newBlockNumber || state.newBlockNumber <= oldState.newBlockNumber) return $el.empty().append(state.currentCoinBalance) updateAllCalculatedUsdValues() } }, '[data-selector="last-balance-update"]': { render ($el, state, oldState) { - if (!state.newBlockNumber || state.newBlockNumber > oldState.newBlockNumber) return + if (!state.newBlockNumber || state.newBlockNumber <= oldState.newBlockNumber) return $el.empty().append(state.currentCoinBalanceBlockNumber) } }, @@ -306,11 +306,12 @@ if ($addressDetailsPage.length) { msg: humps.camelizeKeys(msg) })) - addressChannel.push('get_balance', {}) - .receive('ok', (msg) => store.dispatch({ - type: 'RECEIVED_UPDATED_BALANCE', - msg: humps.camelizeKeys(msg) - })) + // following lines causes double /token-balances request + // addressChannel.push('get_balance', {}) + // .receive('ok', (msg) => store.dispatch({ + // type: 'RECEIVED_UPDATED_BALANCE', + // msg: humps.camelizeKeys(msg) + // })) loadCounters(store) diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js index 50fece655dc5..fe99a6e7e1c6 100644 --- a/apps/block_scout_web/assets/js/pages/address/logs.js +++ b/apps/block_scout_web/assets/js/pages/address/logs.js @@ -1,8 +1,7 @@ import $ from 'jquery' import omit from 'lodash.omit' -import humps from 'humps' import { connectElements } from '../../lib/redux_helpers.js' -import { createAsyncLoadStore } from '../../lib/async_listing_load' +import { createAsyncLoadStore, loadPage } from '../../lib/async_listing_load' import '../address' import { utils } from 'web3' @@ -57,18 +56,16 @@ const elements = { } if ($('[data-page="address-logs"]').length) { + let timer + const waitTime = 500 + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierLog') const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash const $element = $('[data-async-listing]') connectElements({ store, elements }) - store.dispatch({ - type: 'PAGE_LOAD', - addressHash - }) - - $element.on('click', '[data-search-button]', (_event) => { + const searchFunc = (_event) => { store.dispatch({ type: 'START_SEARCH', addressHash @@ -76,15 +73,32 @@ if ($('[data-page="address-logs"]').length) { const topic = $('[data-search-field]').val() const addressHashPlain = store.getState().addressHash const addressHashChecksum = addressHashPlain && utils.toChecksumAddress(addressHashPlain) - const path = '/search-logs?topic=' + topic + '&address_id=' + addressHashChecksum - store.dispatch({ type: 'START_REQUEST' }) - $.getJSON(path, { type: 'JSON' }) - .done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED' }, humps.camelizeKeys(response)))) - .fail(() => store.dispatch({ type: 'REQUEST_ERROR' })) - .always(() => store.dispatch({ type: 'FINISH_REQUEST' })) + const path = `/search-logs?topic=${topic}&address_id=${addressHashChecksum}` + loadPage(store, path) + } + + store.dispatch({ + type: 'PAGE_LOAD', + addressHash }) + $element.on('click', '[data-search-button]', searchFunc) + $element.on('click', '[data-cancel-search-button]', (_event) => { - window.location.replace(window.location.href.split('?')[0]) + $('[data-search-field]').val('') + loadPage(store, window.location.pathname) + }) + + $element.on('input keyup', '[data-search-field]', (event) => { + if (event.type === 'input') { + clearTimeout(timer) + timer = setTimeout(() => { + searchFunc(event) + }, waitTime) + } + if (event.type === 'keyup' && event.keyCode === 13) { + clearTimeout(timer) + searchFunc(event) + } }) } diff --git a/apps/block_scout_web/assets/js/pages/blocks.js b/apps/block_scout_web/assets/js/pages/blocks.js index bde01fd6c195..734b9d9998ad 100644 --- a/apps/block_scout_web/assets/js/pages/blocks.js +++ b/apps/block_scout_web/assets/js/pages/blocks.js @@ -71,6 +71,7 @@ function withMissingBlocks (reducer) { const blockNumbers = keys(blockNumbersToItems).map(x => parseInt(x, 10)) const minBlock = min(blockNumbers) const maxBlock = max(blockNumbers) + if (maxBlock - minBlock > 100) return result return Object.assign({}, result, { items: rangeRight(minBlock, maxBlock + 1) diff --git a/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js b/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js index e9e0bdc52452..7f51825a5828 100644 --- a/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js +++ b/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js @@ -1,10 +1,11 @@ import $ from 'jquery' +import Cookies from 'js-cookie' -$('.dark-mode-changer').click(function () { - if (localStorage.getItem('current-color-mode') === 'dark') { - localStorage.setItem('current-color-mode', 'light') +$('.dark-mode-changer').on('click', function () { + if (Cookies.get('chakra-ui-color-mode') === 'dark') { + Cookies.set('chakra-ui-color-mode', 'light') } else { - localStorage.setItem('current-color-mode', 'dark') + Cookies.set('chakra-ui-color-mode', 'dark') } // reload each theme switch document.location.reload(true) diff --git a/apps/block_scout_web/assets/js/pages/favorites.js b/apps/block_scout_web/assets/js/pages/favorites.js deleted file mode 100644 index d38abe27fff5..000000000000 --- a/apps/block_scout_web/assets/js/pages/favorites.js +++ /dev/null @@ -1,58 +0,0 @@ -import $ from 'jquery' - -const favoritesContainer = $('.js-favorites-tab') -let favoritesNetworksUrls = [] - -if (localStorage.getItem('favoritesNetworksUrls') === null) { - localStorage.setItem('favoritesNetworksUrls', JSON.stringify(favoritesNetworksUrls)) -} else { - favoritesNetworksUrls = JSON.parse(localStorage.getItem('favoritesNetworksUrls')) -} - -$(document).on('change', ".network-selector-item-favorite input[type='checkbox']", function () { - const networkUrl = $(this).attr('data-url') - const thisStatus = $(this).is(':checked') - const workWith = $(".network-selector-item[data-url='" + networkUrl + "'") - - // Add new checkbox status to same network in another tabs - $(".network-selector-item-favorite input[data-url='" + networkUrl + "']").prop('checked', thisStatus) - - // Clone - const parent = $(".network-selector-item[data-url='" + networkUrl + "'").clone() - - // Push or remove favorite networks to array - const found = $.inArray(networkUrl, favoritesNetworksUrls) - if (found < 0 && thisStatus === true) { - favoritesNetworksUrls.push(networkUrl) - } else { - const index = favoritesNetworksUrls.indexOf(networkUrl) - if (index !== -1) { - favoritesNetworksUrls.splice(index, 1) - } - } - - // Push to localstorage - const willBePushed = JSON.stringify(favoritesNetworksUrls) - localStorage.setItem('favoritesNetworksUrls', willBePushed) - - // Append or remove item from 'favorites' tab - if (thisStatus === true) { - favoritesContainer.append(parent[0]) - $('.js-favorites-tab .network-selector-tab-content-empty').hide() - } else { - const willRemoved = favoritesContainer.find(workWith) - willRemoved.remove() - if (favoritesNetworksUrls.length === 0) { - $('.js-favorites-tab .network-selector-tab-content-empty').show() - } - } -}) - -if (favoritesNetworksUrls.length > 0) { - $('.js-favorites-tab .network-selector-tab-content-empty').hide() - for (let i = 0; i < favoritesNetworksUrls.length + 1; i++) { - $(".network-selector-item[data-url='" + favoritesNetworksUrls[i] + "'").find('input[data-url]').prop('checked', true) - const parent = $(".network-selector-item[data-url='" + favoritesNetworksUrls[i] + "'").clone() - favoritesContainer.append(parent[0]) - } -} diff --git a/apps/block_scout_web/assets/js/pages/network-search.js b/apps/block_scout_web/assets/js/pages/network-search.js deleted file mode 100644 index 5b5bbbb1e170..000000000000 --- a/apps/block_scout_web/assets/js/pages/network-search.js +++ /dev/null @@ -1,21 +0,0 @@ -import $ from 'jquery' - -const networkSearchInput = $('.network-selector-search-input') -let networkSearchInputVal = '' - -$(networkSearchInput).on('input', function () { - networkSearchInputVal = $(this).val() - - $.expr[':'].Contains = $.expr.createPseudo(function (arg) { - return function (elem) { - return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0 - } - }) - - if (networkSearchInputVal === '') { - $('.network-selector-item').show() - } else { - $('.network-selector-item').hide() - $(".network-selector-item:Contains('" + networkSearchInputVal + "')").show() - } -}) diff --git a/apps/block_scout_web/assets/js/pages/sol2uml.js b/apps/block_scout_web/assets/js/pages/sol2uml.js new file mode 100644 index 000000000000..253f268a6bf8 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/sol2uml.js @@ -0,0 +1,82 @@ +import 'viewerjs/dist/viewer.min.css' +import Viewer from 'viewerjs' +import $ from 'jquery' +import { createStore, connectElements } from '../lib/redux_helpers.js' + +export const initialState = { + contract_svg: null, + visualize_error: null +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'SVG_FETCHED': { + return Object.assign({}, state, { + contract_svg: action.contract_svg, + visualize_error: action.error + }) + } + default: + return state + } +} + +const elements = { + '[data-selector="contract-image"]': { + render ($el, state, oldState) { + if (state.contract_svg) { + $('#spinner').hide() + $('#gallery img').attr('src', 'data:image/svg+xml;base64,' + state.contract_svg) + const gallery = document.getElementById('gallery') + const viewer = new Viewer(gallery, { + inline: false, + toolbar: { + zoomIn: 2, + zoomOut: 4, + oneToOne: 4, + reset: 4, + play: { + show: 4, + size: 'large' + }, + rotateLeft: 4, + rotateRight: 4, + flipHorizontal: 4, + flipVertical: 4 + } + }) + viewer.update() + $el.show() + } else if (state.visualize_error) { + $('#spinner').hide() + $el.empty().text('Cannot visualize contract: ' + state.visualize_error) + $el.show() + } else { + $('#spinner').show() + $el.hide() + } + } + } +} + +function loadSvg (store) { + const $element = $('[data-async-contract-svg]') + const path = $element.data().asyncContractSvg + + function fetchSvg () { + $.getJSON(path) + .done((response) => { + store.dispatch(Object.assign({ type: 'SVG_FETCHED' }, response)) + }) + } + + fetchSvg() +} + +function main () { + const store = createStore(reducer) + connectElements({ store, elements }) + loadSvg(store) +} + +main() diff --git a/apps/block_scout_web/assets/js/pages/token/search.js b/apps/block_scout_web/assets/js/pages/token/search.js index 66e0307287b4..a3949f2019a3 100644 --- a/apps/block_scout_web/assets/js/pages/token/search.js +++ b/apps/block_scout_web/assets/js/pages/token/search.js @@ -25,6 +25,9 @@ export function reducer (state, action) { } if ($('[data-page="tokens"]').length) { + let timer + const waitTime = 500 + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') store.dispatch({ @@ -32,21 +35,24 @@ if ($('[data-page="tokens"]').length) { }) $searchInput.on('input', (event) => { - const value = $(event.target).val() - - const loc = window.location.pathname - - if (value.length >= 3 || value === '') { - store.dispatch({ type: 'START_SEARCH' }) - store.dispatch({ type: 'START_REQUEST' }) - $.ajax({ - url: `${loc}?type=JSON&filter=${value}`, - type: 'GET', - dataType: 'json', - contentType: 'application/json; charset=utf-8' - }).done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED' }, humps.camelizeKeys(response)))) - .fail(() => store.dispatch({ type: 'REQUEST_ERROR' })) - .always(() => store.dispatch({ type: 'FINISH_REQUEST' })) - } + clearTimeout(timer) + timer = setTimeout(() => { + const value = $(event.target).val() + + const loc = window.location.pathname + + if (value.length >= 3 || value === '') { + store.dispatch({ type: 'START_SEARCH' }) + store.dispatch({ type: 'START_REQUEST' }) + $.ajax({ + url: `${loc}?type=JSON&filter=${value}`, + type: 'GET', + dataType: 'json', + contentType: 'application/json; charset=utf-8' + }).done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED' }, humps.camelizeKeys(response)))) + .fail(() => store.dispatch({ type: 'REQUEST_ERROR' })) + .always(() => store.dispatch({ type: 'FINISH_REQUEST' })) + } + }, waitTime) }) } diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index 0c82341909d6..373ef05e62b7 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -57,7 +57,8 @@ if ($transactionDetailsPage.length) { pathParts.includes('token-transfers') || pathParts.includes('logs') || pathParts.includes('token-transfers') || - pathParts.includes('raw-trace') + pathParts.includes('raw-trace') || + pathParts.includes('state') if (shouldScroll) { document.getElementById('transaction-tabs').scrollIntoView() } 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 1db734028c0d..687eced49c8d 100644 --- a/apps/block_scout_web/assets/js/pages/verification_form.js +++ b/apps/block_scout_web/assets/js/pages/verification_form.js @@ -47,38 +47,12 @@ const elements = { render ($el, state) { if (state.newForm) { $el.replaceWith(state.newForm) - state.newForm = null - $('button[data-button-loading="animation"]').click(_event => { - $('#loading').removeClass('d-none') - }) + if ($('.nightly-builds-true').prop('checked')) { filterNightlyBuilds(false, false) } + if ($('.nightly-builds-false').prop('checked')) { filterNightlyBuilds(true, false) } - $(function () { - $('.js-btn-add-contract-libraries').on('click', function () { - $('.js-smart-contract-libraries-wrapper').show() - $(this).hide() - }) - - $('.js-smart-contract-form-reset').on('click', function () { - $('.js-contract-library-form-group').removeClass('active') - $('.js-contract-library-form-group').first().addClass('active') - $('.js-smart-contract-libraries-wrapper').hide() - $('.js-btn-add-contract-libraries').show() - $('.js-add-contract-library-wrapper').show() - }) - - $('.js-btn-add-contract-library').on('click', function () { - const nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group') - - if (nextContractLibrary) { - nextContractLibrary.addClass('active') - } - - if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) { - $('.js-add-contract-library-wrapper').hide() - } - }) - }) + initializeDropzone() + state.newForm = null return $el } @@ -90,15 +64,21 @@ const elements = { const $contractVerificationPage = $('[data-page="contract-verification"]') const $contractVerificationChooseTypePage = $('[data-page="contract-verification-choose-type"]') -function filterNightlyBuilds (filter) { +function filterNightlyBuilds (filter, selectFirstNonNightly_) { const select = document.getElementById('smart_contract_compiler_version') const options = select.getElementsByTagName('option') + let selectFirstNonNightly = selectFirstNonNightly_ + for (const option of options) { const txtValue = option.textContent || option.innerText if (filter) { if (txtValue.toLowerCase().indexOf('nightly') > -1) { option.style.display = 'none' } else { + if (selectFirstNonNightly) { + option.selected = 'selected' + selectFirstNonNightly = false + } option.style.display = '' } } else { @@ -109,6 +89,8 @@ function filterNightlyBuilds (filter) { } } +let dropzone + if ($contractVerificationPage.length) { window.onbeforeunload = () => { window.loading = true @@ -133,109 +115,47 @@ if ($contractVerificationPage.length) { msg: humps.camelizeKeys(msg) })) - $('button[data-button-loading="animation"]').click(_event => { + $('body').on('click', 'button[data-button-loading="animation"]', function () { $('#loading').removeClass('d-none') }) $(function () { - function standardJSONBehavior () { - $('#standard-json-dropzone-form').removeClass('dz-clickable') - this.on('addedfile', function (_file) { - $('#verify-via-standard-json-input-submit').prop('disabled', false) - $('#file-help-block').text('') - $('#dropzone-previews').addClass('dz-started') - }) - - this.on('removedfile', function (_file) { - if (this.files.length === 0) { - $('#verify-via-standard-json-input-submit').prop('disabled', true) - $('#dropzone-previews').removeClass('dz-started') - } - }) - } - - function metadataJSONBehavior () { - $('#metadata-json-dropzone-form').removeClass('dz-clickable') - this.on('addedfile', function (_file) { - changeVisibilityOfVerifyButton(this.files.length) - $('#file-help-block').text('') - $('#dropzone-previews').addClass('dz-started') - }) - - this.on('removedfile', function (_file) { - changeVisibilityOfVerifyButton(this.files.length) - if (this.files.length === 0) { - $('#dropzone-previews').removeClass('dz-started') - } - }) - } - - const $jsonDropzoneMetadata = $('#metadata-json-dropzone-form') - const $jsonDropzoneStandardInput = $('#standard-json-dropzone-form') - - if ($jsonDropzoneMetadata.length || $jsonDropzoneStandardInput.length) { - const func = $jsonDropzoneMetadata.length ? metadataJSONBehavior : standardJSONBehavior - const maxFiles = $jsonDropzoneMetadata.length ? 100 : 1 - const acceptedFiles = $jsonDropzoneMetadata.length ? 'text/plain,application/json,.sol,.json' : 'text/plain,application/json,.json' - const tag = $jsonDropzoneMetadata.length ? '#metadata-json-dropzone-form' : '#standard-json-dropzone-form' - const jsonVerificationType = $jsonDropzoneMetadata.length ? 'json:metadata' : 'json:standard' - - var dropzone = new Dropzone(tag, { - autoProcessQueue: false, - acceptedFiles, - parallelUploads: 100, - uploadMultiple: true, - addRemoveLinks: true, - maxFilesize: 10, - maxFiles, - previewsContainer: '#dropzone-previews', - params: { address_hash: $('#smart_contract_address_hash').val(), verification_type: jsonVerificationType }, - init: func - }) - } - - function changeVisibilityOfVerifyButton (filesLength) { - if (filesLength > 0) { - $('#verify-via-metadata-json-submit').prop('disabled', false) - } else { - $('#verify-via-metadata-json-submit').prop('disabled', true) - } - } + initializeDropzone() setTimeout(function () { $('.nightly-builds-false').trigger('click') }, 10) - $('.js-btn-add-contract-libraries').on('click', function () { + $('body').on('click', '.js-btn-add-contract-libraries', function () { $('.js-smart-contract-libraries-wrapper').show() $(this).hide() }) - $('.autodetectfalse').on('click', function () { + $('body').on('click', '.autodetectfalse', function () { if ($(this).prop('checked')) { $('.constructor-arguments').show() } }) - $('.autodetecttrue').on('click', function () { + $('body').on('click', '.autodetecttrue', function () { if ($(this).prop('checked')) { $('.constructor-arguments').hide() } }) - $('.nightly-builds-true').on('click', function () { - if ($(this).prop('checked')) { filterNightlyBuilds(false) } + $('body').on('click', '.nightly-builds-true', function () { + if ($(this).prop('checked')) { filterNightlyBuilds(false, true) } }) - $('.nightly-builds-false').on('click', function () { - if ($(this).prop('checked')) { filterNightlyBuilds(true) } + $('body').on('click', '.nightly-builds-false', function () { + if ($(this).prop('checked')) { filterNightlyBuilds(true, true) } }) - $('.optimization-false').on('click', function () { + $('body').on('click', '.optimization-false', function () { if ($(this).prop('checked')) { $('.optimization-runs').hide() } }) - $('.optimization-true').on('click', function () { + $('body').on('click', '.optimization-true', function () { if ($(this).prop('checked')) { $('.optimization-runs').show() } }) - $('.js-smart-contract-form-reset').on('click', function () { + $('body').on('click', '.js-smart-contract-form-reset', function () { $('.js-contract-library-form-group').removeClass('active') $('.js-contract-library-form-group').first().addClass('active') $('.js-smart-contract-libraries-wrapper').hide() @@ -243,7 +163,7 @@ if ($contractVerificationPage.length) { $('.js-add-contract-library-wrapper').show() }) - $('.js-btn-add-contract-library').on('click', function () { + $('body').on('click', '.js-btn-add-contract-library', (event) => { const nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group') if (nextContractLibrary) { @@ -255,7 +175,7 @@ if ($contractVerificationPage.length) { } }) - $('#verify-via-standard-json-input-submit').on('click', (event) => { + $('body').on('click', '#verify-via-standard-json-input-submit', (event) => { event.preventDefault() if (dropzone.files.length > 0) { dropzone.processQueue() @@ -264,7 +184,14 @@ if ($contractVerificationPage.length) { } }) - $('#verify-via-metadata-json-submit').on('click', (event) => { + $('body').on('click', '[data-submit-button]', (event) => { + // submit form without page updating in order to avoid websocket reconnecting + event.preventDefault() + const $form = $('form')[0] + $.post($form.action, convertFormToJSON($form)) + }) + + $('body').on('click', '#verify-via-metadata-json-submit', (event) => { event.preventDefault() if (dropzone.files.length > 0) { dropzone.processQueue() @@ -279,7 +206,8 @@ if ($contractVerificationPage.length) { $('#verify_via_flattened_code_button').show() $('#verify_via_sourcify_button').hide() $('#verify_vyper_contract_button').hide() - $('#verify_via_standard_json_input').hide() + $('#verify_via_standard_json_input_button').hide() + $('#verify_via_multi_part_files_button').hide() } }) @@ -288,7 +216,8 @@ if ($contractVerificationPage.length) { $('#verify_via_flattened_code_button').hide() $('#verify_via_sourcify_button').show() $('#verify_vyper_contract_button').hide() - $('#verify_via_standard_json_input').hide() + $('#verify_via_standard_json_input_button').hide() + $('#verify_via_multi_part_files_button').hide() } }) @@ -297,7 +226,8 @@ if ($contractVerificationPage.length) { $('#verify_via_flattened_code_button').hide() $('#verify_via_sourcify_button').hide() $('#verify_vyper_contract_button').show() - $('#verify_via_standard_json_input').hide() + $('#verify_via_standard_json_input_button').hide() + $('#verify_via_multi_part_files_button').hide() } }) @@ -306,7 +236,132 @@ if ($contractVerificationPage.length) { $('#verify_via_flattened_code_button').hide() $('#verify_via_sourcify_button').hide() $('#verify_vyper_contract_button').hide() - $('#verify_via_standard_json_input').show() + $('#verify_via_standard_json_input_button').show() + $('#verify_via_multi_part_files_button').hide() + } + }) + + $('.verify-via-multi-part-files').on('click', function () { + if ($(this).prop('checked')) { + $('#verify_via_flattened_code_button').hide() + $('#verify_via_sourcify_button').hide() + $('#verify_vyper_contract_button').hide() + $('#verify_via_standard_json_input_button').hide() + $('#verify_via_multi_part_files_button').show() } }) } + +function convertFormToJSON (form) { + const array = $(form).serializeArray() + const json = {} + $.each(array, function () { + json[this.name] = this.value || '' + }) + return json +} + +function changeVisibilityOfVerifyButton (filesLength) { + if (filesLength > 0) { + $('#verify-via-metadata-json-submit').prop('disabled', false) + } else { + $('#verify-via-metadata-json-submit').prop('disabled', true) + } +} + +function standardJSONBehavior () { + $('#standard-json-dropzone-form').removeClass('dz-clickable') + this.on('addedfile', function (_file) { + $('#verify-via-standard-json-input-submit').prop('disabled', false) + $('#file-help-block').text('') + $('#dropzone-previews').addClass('dz-started') + }) + + this.on('removedfile', function (_file) { + if (this.files.length === 0) { + $('#verify-via-standard-json-input-submit').prop('disabled', true) + $('#dropzone-previews').removeClass('dz-started') + } + }) +} + +function metadataJSONBehavior () { + $('#metadata-json-dropzone-form').removeClass('dz-clickable') + this.on('addedfile', function (_file) { + changeVisibilityOfVerifyButton(this.files.length) + $('#file-help-block').text('') + $('#dropzone-previews').addClass('dz-started') + }) + + this.on('removedfile', function (_file) { + changeVisibilityOfVerifyButton(this.files.length) + if (this.files.length === 0) { + $('#dropzone-previews').removeClass('dz-started') + } + }) +} + +function multiPartFilesBehavior () { + $('#metadata-json-dropzone-form').removeClass('dz-clickable') + this.on('addedfile', function (_file) { + changeVisibilityOfVerifyButton(this.files.length) + $('#file-help-block').text('') + $('#dropzone-previews').addClass('dz-started') + }) + + this.on('removedfile', function (_file) { + changeVisibilityOfVerifyButton(this.files.length) + if (this.files.length === 0) { + $('#dropzone-previews').removeClass('dz-started') + } + }) +} + +function initializeDropzone () { + const $jsonDropzoneMetadata = $('#metadata-json-dropzone-form') + const $jsonDropzoneStandardInput = $('#standard-json-dropzone-form') + + if ($jsonDropzoneMetadata.length || $jsonDropzoneStandardInput.length) { + const func = $jsonDropzoneMetadata.length ? metadataJSONBehavior : standardJSONBehavior + const maxFiles = $jsonDropzoneMetadata.length ? 100 : 1 + const acceptedFiles = $jsonDropzoneMetadata.length ? 'text/plain,application/json,.sol,.json' : 'text/plain,application/json,.json' + const tag = $jsonDropzoneMetadata.length ? '#metadata-json-dropzone-form' : '#standard-json-dropzone-form' + const jsonVerificationType = $jsonDropzoneMetadata.length ? 'json:metadata' : 'json:standard' + + dropzone = new Dropzone(tag, { + autoProcessQueue: false, + acceptedFiles, + parallelUploads: 100, + uploadMultiple: true, + addRemoveLinks: true, + maxFilesize: 10, + maxFiles, + previewsContainer: '#dropzone-previews', + params: { address_hash: $('#smart_contract_address_hash').val(), verification_type: jsonVerificationType }, + init: func + }) + } + + const $dropzoneMultiPartFiles = $('#multi-part-dropzone-form') + + if ($dropzoneMultiPartFiles.length) { + const func = multiPartFilesBehavior + const maxFiles = 100 + const acceptedFiles = 'text/plain,.sol' + const tag = '#multi-part-dropzone-form' + const jsonVerificationType = 'multi-part-files' + + dropzone = new Dropzone(tag, { + autoProcessQueue: false, + acceptedFiles, + parallelUploads: 100, + uploadMultiple: true, + addRemoveLinks: true, + maxFilesize: 10, + maxFiles, + previewsContainer: '#dropzone-previews', + params: { address_hash: $('#smart_contract_address_hash').val(), verification_type: jsonVerificationType }, + init: func + }) + } +} diff --git a/apps/block_scout_web/assets/js/pages/verified_contracts.js b/apps/block_scout_web/assets/js/pages/verified_contracts.js new file mode 100644 index 000000000000..f1727412b2fb --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/verified_contracts.js @@ -0,0 +1,94 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import { loadPage, createAsyncLoadStore } from '../lib/async_listing_load' +import { connectElements } from '../lib/redux_helpers.js' + +export const initialState = { + isSearch: false +} + +const elements = { + '[data-search-field]': { + render ($el, state) { + return $el + } + }, + '[data-search-button]': { + render ($el, state) { + return $el + } + }, + '[data-cancel-search-button]': { + render ($el, state) { + if (!state.isSearch) { + return $el.hide() + } + + return $el.show() + } + }, + '[data-search]': { + render ($el, state) { + if (state.emptyResponse && !state.isSearch) { + return $el.hide() + } + + return $el.show() + } + } +} + +export function reducer (state, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'START_SEARCH': { + return Object.assign({}, state, { pagesStack: [], isSearch: true }) + } + default: + return state + } +} + +if ($('[data-page="verified-contracts-list"]').length) { + let timer + const waitTime = 500 + + const $element = $('[data-async-listing]') + + $element.on('click', '[data-next-page-button], [data-prev-page-button]', (event) => { + document.getElementById('verified-contracts-list').scrollIntoView() + }) + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + + connectElements({ store, elements }) + + const searchFunc = (_event) => { + store.dispatch({ type: 'START_SEARCH' }) + const searchInput = $('[data-search-field]').val() + const pathHaveNoParams = window.location.pathname + '?search=' + searchInput + const pathHaveParams = window.location.pathname + window.location.search + '&search=' + searchInput + const path = window.location.href.includes('?') ? pathHaveParams : pathHaveNoParams + loadPage(store, path) + } + + store.dispatch({ + type: 'PAGE_LOAD' + }) + + $element.on('input keyup', '[data-search-field]', (event) => { + if (event.type === 'input') { + clearTimeout(timer) + timer = setTimeout(() => { + searchFunc(event) + }, waitTime) + } + if (event.type === 'keyup' && event.keyCode === 13) { + clearTimeout(timer) + searchFunc(event) + } + }) +} diff --git a/apps/block_scout_web/assets/js/socket.js b/apps/block_scout_web/assets/js/socket.js index 76c110acdb3d..1dc41b553800 100644 --- a/apps/block_scout_web/assets/js/socket.js +++ b/apps/block_scout_web/assets/js/socket.js @@ -2,9 +2,12 @@ import { Socket } from 'phoenix' import { locale } from './locale' let websocketRootUrl = process.env.SOCKET_ROOT -if (!websocketRootUrl || websocketRootUrl === '/') { +if (!websocketRootUrl) { websocketRootUrl = '' } +if (websocketRootUrl.endsWith('/')) { + websocketRootUrl = websocketRootUrl.slice(0, -1) +} const socket = new Socket(websocketRootUrl + '/socket', { params: { locale } }) socket.connect() diff --git a/apps/block_scout_web/assets/js/view_specific/address_contract/code_highlighting.js b/apps/block_scout_web/assets/js/view_specific/address_contract/code_highlighting.js index fde8bc2b135a..2a0068a23678 100644 --- a/apps/block_scout_web/assets/js/view_specific/address_contract/code_highlighting.js +++ b/apps/block_scout_web/assets/js/view_specific/address_contract/code_highlighting.js @@ -9,20 +9,22 @@ const Mode = ace.require('ace/mode/csharp').Mode const codeMain = $('#code_viewer_main') const code = codeMain.text() /* eslint-disable-next-line */ -const editor = ace.edit('code_viewer_main') -editor.session.setMode(new Mode()) -editor.setTheme('ace/theme/chrome') -editor.setValue(code, -1) -editor.setOptions({ maxLines: 40, readOnly: true, printMargin: false }) - -const len = codeMain.data('additional-sources-length') -for (let i = 0; i < len; i++) { - const tag = 'code_viewer_' + i - const code = $('#' + tag).text() - /* eslint-disable-next-line */ - const editor = ace.edit(tag) +const editor = (codeMain.length > 0) && ace.edit('code_viewer_main') +if (editor) { editor.session.setMode(new Mode()) editor.setTheme('ace/theme/chrome') editor.setValue(code, -1) - editor.setOptions({ maxLines: 40, readOnly: true }) + editor.setOptions({ maxLines: 40, readOnly: true, printMargin: false }) + + const len = codeMain.data('additional-sources-length') + for (let i = 0; i < len; i++) { + const tag = 'code_viewer_' + i + const code = $('#' + tag).text() + /* eslint-disable-next-line */ + const editor = ace.edit(tag) + editor.session.setMode(new Mode()) + editor.setTheme('ace/theme/chrome') + editor.setValue(code, -1) + editor.setOptions({ maxLines: 40, readOnly: true }) + } } diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ffa94908b3b7..b75bed50043c 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,23 +7,24 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@fortawesome/fontawesome-free": "^6.0.0-beta3", - "@tarekraafat/autocomplete.js": "^10.2.6", - "@walletconnect/web3-provider": "^1.0.8", + "@fortawesome/fontawesome-free": "^6.2.1", + "@tarekraafat/autocomplete.js": "^10.2.7", + "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.0.0", - "bignumber.js": "^9.0.2", + "bignumber.js": "^9.1.1", "bootstrap": "^4.6.0", - "chart.js": "^3.7.0", - "chartjs-adapter-luxon": "^1.1.0", - "clipboard": "^2.0.9", - "core-js": "^3.20.3", + "chart.js": "^4.0.1", + "chartjs-adapter-luxon": "^1.3.0", + "clipboard": "^2.0.11", + "core-js": "^3.26.1", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", - "eth-net-props": "^1.0.33", - "highlight.js": "^11.4.0", + "eth-net-props": "^1.0.41", + "highlight.js": "^11.7.0", "https-browserify": "^1.0.0", "humps": "^2.0.1", - "jquery": "^3.4.0", + "jquery": "^3.6.2", + "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", "lodash.differenceby": "^4.8.0", "lodash.find": "^4.6.0", @@ -42,51 +43,54 @@ "lodash.omit": "^4.5.0", "lodash.rangeright": "^4.2.0", "lodash.reduce": "^4.6.0", - "luxon": "^2.4.0", - "moment": "^2.29.2", + "luxon": "^3.1.1", + "moment": "^2.29.4", "nanomorph": "^5.4.0", "numeral": "^2.0.6", "os-browserify": "^0.3.0", - "path-parser": "^4.2.0", + "path-parser": "^6.1.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", + "photoswipe": "^5.3.4", "pikaday": "^1.8.2", "popper.js": "^1.14.7", - "reduce-reducers": "^0.4.3", - "redux": "^4.0.5", + "reduce-reducers": "^1.0.4", + "redux": "^4.2.0", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.3.10", + "sweetalert2": "^11.6.15", "urijs": "^1.19.11", "url": "^0.11.0", - "util": "^0.12.3", - "web3": "^1.7.0", - "web3modal": "^1.9.5", - "xss": "^1.0.10" + "util": "^0.12.5", + "viewerjs": "^1.11.1", + "web3": "^1.8.1", + "web3modal": "^1.9.10", + "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.16.12", - "@babel/preset-env": "^7.16.11", - "autoprefixer": "^10.4.2", - "babel-loader": "^8.2.3", - "copy-webpack-plugin": "^10.2.1", + "@babel/core": "^7.20.5", + "@babel/preset-env": "^7.20.2", + "autoprefixer": "^10.4.13", + "babel-loader": "^9.1.0", + "copy-webpack-plugin": "^11.0.0", "css-loader": "^5.2.7", - "css-minimizer-webpack-plugin": "^3.4.1", - "eslint": "^8.17.0", + "css-minimizer-webpack-plugin": "^4.2.2", + "eslint": "^8.29.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-promise": "^6.1.1", "file-loader": "^6.2.0", - "jest": "^27.4.7", - "mini-css-extract-plugin": "^2.5.3", - "postcss": "^8.4.6", - "postcss-loader": "^6.2.1", - "sass": "^1.49.8", - "sass-loader": "^12.6.0", + "jest": "^29.3.1", + "jest-environment-jsdom": "^29.3.1", + "mini-css-extract-plugin": "^2.7.2", + "postcss": "^8.4.20", + "postcss-loader": "^7.0.2", + "sass": "^1.56.2", + "sass-loader": "^13.2.0", "style-loader": "^3.3.1", - "webpack": "^5.69.1", - "webpack-cli": "^4.9.2" + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" }, "engines": { "node": "16.x", @@ -94,51 +98,74 @@ } }, "../../../deps/phoenix": { - "version": "1.5.13", - "license": "MIT" + "version": "0.0.1" }, "../../../deps/phoenix_html": { - "version": "3.0.4" + "version": "0.0.1" + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "dependencies": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", + "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", - "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.12", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.10", - "@babel/types": "^7.16.8", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.1", + "semver": "^6.3.0" }, "engines": { "node": ">=6.9.0" @@ -149,50 +176,50 @@ } }, "node_modules/@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "dependencies": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", "dev": true, "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", "dependencies": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", "semver": "^6.3.0" }, "engines": { @@ -203,18 +230,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.10.tgz", - "integrity": "sha512-wDeej0pu3WN/ffTxMNCPW5UCiOav8IcLRxSIyp/9+IF2xJUM9h/OYjg0IJLHaL6F8oU8kqMz9nc1vryXhMsgXg==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -224,13 +251,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.7.tgz", - "integrity": "sha512-fk5A6ymfp+O5+p2yCkXAu5Kyj6v0xh0RBeNcAkYUMDvvAAoxvSKXn+Jb37t/yWFiQVDFK1ELpUTD8/aLhCPu+g==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" }, "engines": { "node": ">=6.9.0" @@ -240,14 +267,12 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", "dependencies": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2", @@ -258,238 +283,235 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dependencies": { - "@babel/types": "^7.16.7" - }, + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", "dev": true, "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", "dev": true, "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", "dev": true, "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.20.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.18.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -498,9 +520,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", - "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -509,12 +531,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", - "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -524,14 +546,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", - "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -541,13 +563,14 @@ } }, "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", "@babel/plugin-syntax-async-generators": "^7.8.4" }, "engines": { @@ -558,13 +581,13 @@ } }, "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -574,13 +597,13 @@ } }, "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz", - "integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -591,12 +614,12 @@ } }, "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -607,12 +630,12 @@ } }, "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -623,12 +646,12 @@ } }, "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", - "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -639,12 +662,12 @@ } }, "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -655,12 +678,12 @@ } }, "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -671,12 +694,12 @@ } }, "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -687,16 +710,16 @@ } }, "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.7.tgz", - "integrity": "sha512-3O0Y4+dw94HA86qSg9IHfyPktgR7q3gpNVAeiKQd+8jBKFaU5NQS1Yatgo4wY+UFNuLjvxcSmzcsHqrhgTyBUA==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.16.7" + "@babel/plugin-transform-parameters": "^7.20.1" }, "engines": { "node": ">=6.9.0" @@ -706,12 +729,12 @@ } }, "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -722,13 +745,13 @@ } }, "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -739,13 +762,13 @@ } }, "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -755,14 +778,14 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -773,13 +796,13 @@ } }, "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", - "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=4" @@ -863,6 +886,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -887,6 +925,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -990,12 +1043,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.19.0" }, "engines": { "node": ">=6.9.0" @@ -1005,12 +1058,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1020,14 +1073,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1037,12 +1090,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1052,12 +1105,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz", + "integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -1067,18 +1120,19 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", "globals": "^11.1.0" }, "engines": { @@ -1089,12 +1143,12 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1104,12 +1158,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz", - "integrity": "sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -1119,13 +1173,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1135,12 +1189,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", - "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1150,13 +1204,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1166,12 +1220,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1181,14 +1235,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1198,12 +1252,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1213,12 +1267,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1228,14 +1282,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", - "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" }, "engines": { "node": ">=6.9.0" @@ -1245,15 +1298,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" }, "engines": { "node": ">=6.9.0" @@ -1263,16 +1315,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", - "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", "dev": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" }, "engines": { "node": ">=6.9.0" @@ -1282,13 +1333,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", - "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1298,12 +1349,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" }, "engines": { "node": ">=6.9.0" @@ -1313,12 +1365,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", - "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1328,13 +1380,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1344,12 +1396,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz", + "integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -1359,12 +1411,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1374,12 +1426,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", "dev": true, "dependencies": { - "regenerator-transform": "^0.14.2" + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" }, "engines": { "node": ">=6.9.0" @@ -1389,12 +1442,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", - "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1435,12 +1488,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1450,13 +1503,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1466,12 +1519,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1481,12 +1534,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1496,12 +1549,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1511,12 +1564,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1526,13 +1579,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1542,37 +1595,38 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.11", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -1582,44 +1636,44 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.8", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", "semver": "^6.3.0" }, "engines": { @@ -1629,6 +1683,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-modules": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", @@ -1657,31 +1723,31 @@ } }, "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", - "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.10", - "@babel/types": "^7.16.8", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1690,11 +1756,12 @@ } }, "node_modules/@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1740,14 +1807,14 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "node_modules/@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.2", + "espree": "^9.4.0", "globals": "^13.15.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -1757,6 +1824,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/argparse": { @@ -1766,9 +1836,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1817,43 +1887,53 @@ } }, "node_modules/@ethereumjs/common": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.0.tgz", - "integrity": "sha512-Cq2qS0FTu6O2VU1sgg+WyU9Ps0M6j/BEMHN+hRaECXCV/r0aI78u4N6p52QW/BDVhwWZpCdrvG8X7NJdzlpNUA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz", + "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==", "dependencies": { "crc-32": "^1.2.0", - "ethereumjs-util": "^7.1.3" + "ethereumjs-util": "^7.1.1" } }, "node_modules/@ethereumjs/tx": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.4.0.tgz", - "integrity": "sha512-WWUwg1PdjHKZZxPPo274ZuPsJCWV3SqATrEKQP1n2DrVYVP1aZIYpo/mFaA0BDoE0tIQmBeimRCEA0Lgil+yYw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz", + "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==", "dependencies": { - "@ethereumjs/common": "^2.6.0", - "ethereumjs-util": "^7.1.3" + "@ethereumjs/common": "^2.5.0", + "ethereumjs-util": "^7.1.2" } }, "node_modules/@ethersproject/abi": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.7.tgz", - "integrity": "sha512-Cqktk+hSIckwP/W8O47Eef60VwmoSC/L3lY0+dIBhQPCNn9E4V7rwmm2aFrNRRDJfFlGuZ1khkQUOc3oBX+niw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], "dependencies": { - "@ethersproject/address": "^5.0.4", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/hash": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/strings": "^5.0.4" + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" } }, "node_modules/@ethersproject/abstract-provider": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz", - "integrity": "sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", "funding": [ { "type": "individual", @@ -1865,19 +1945,19 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/networks": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/web": "^5.5.0" + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" } }, "node_modules/@ethersproject/abstract-signer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz", - "integrity": "sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", "funding": [ { "type": "individual", @@ -1889,17 +1969,17 @@ } ], "dependencies": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0" + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" } }, "node_modules/@ethersproject/address": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.5.0.tgz", - "integrity": "sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", "funding": [ { "type": "individual", @@ -1911,17 +1991,17 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/rlp": "^5.5.0" + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" } }, "node_modules/@ethersproject/base64": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.5.0.tgz", - "integrity": "sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", "funding": [ { "type": "individual", @@ -1933,13 +2013,13 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0" + "@ethersproject/bytes": "^5.7.0" } }, "node_modules/@ethersproject/bignumber": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.5.0.tgz", - "integrity": "sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", "funding": [ { "type": "individual", @@ -1951,20 +2031,15 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "bn.js": "^4.11.9" + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" } }, - "node_modules/@ethersproject/bignumber/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/@ethersproject/bytes": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.5.0.tgz", - "integrity": "sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", "funding": [ { "type": "individual", @@ -1976,13 +2051,13 @@ } ], "dependencies": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/constants": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.5.0.tgz", - "integrity": "sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", "funding": [ { "type": "individual", @@ -1994,13 +2069,13 @@ } ], "dependencies": { - "@ethersproject/bignumber": "^5.5.0" + "@ethersproject/bignumber": "^5.7.0" } }, "node_modules/@ethersproject/hash": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.5.0.tgz", - "integrity": "sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", "funding": [ { "type": "individual", @@ -2012,20 +2087,21 @@ } ], "dependencies": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" } }, "node_modules/@ethersproject/keccak256": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.5.0.tgz", - "integrity": "sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", "funding": [ { "type": "individual", @@ -2037,14 +2113,14 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", + "@ethersproject/bytes": "^5.7.0", "js-sha3": "0.8.0" } }, "node_modules/@ethersproject/logger": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.5.0.tgz", - "integrity": "sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", "funding": [ { "type": "individual", @@ -2057,9 +2133,9 @@ ] }, "node_modules/@ethersproject/networks": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.5.2.tgz", - "integrity": "sha512-NEqPxbGBfy6O3x4ZTISb90SjEDkWYDUbEeIFhJly0F7sZjoQMnj5KYzMSkMkLKZ+1fGpx00EDpHQCy6PrDupkQ==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", "funding": [ { "type": "individual", @@ -2071,13 +2147,13 @@ } ], "dependencies": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/properties": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.5.0.tgz", - "integrity": "sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", "funding": [ { "type": "individual", @@ -2089,13 +2165,13 @@ } ], "dependencies": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/rlp": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.5.0.tgz", - "integrity": "sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", "funding": [ { "type": "individual", @@ -2107,14 +2183,14 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/signing-key": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.5.0.tgz", - "integrity": "sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", "funding": [ { "type": "individual", @@ -2126,23 +2202,18 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "bn.js": "^4.11.9", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", "elliptic": "6.5.4", "hash.js": "1.1.7" } }, - "node_modules/@ethersproject/signing-key/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/@ethersproject/strings": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.5.0.tgz", - "integrity": "sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", "funding": [ { "type": "individual", @@ -2154,15 +2225,15 @@ } ], "dependencies": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" } }, "node_modules/@ethersproject/transactions": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.5.0.tgz", - "integrity": "sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", "funding": [ { "type": "individual", @@ -2174,21 +2245,21 @@ } ], "dependencies": { - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/rlp": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0" + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" } }, "node_modules/@ethersproject/web": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.5.1.tgz", - "integrity": "sha512-olvLvc1CB12sREc1ROPSHTdFCdvMh0J5GSJYiQg2D0hdD4QmJDy8QYDb1CvoqD/bF1c++aeKv2sR5uduuG9dQg==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", "funding": [ { "type": "individual", @@ -2200,34 +2271,26 @@ } ], "dependencies": { - "@ethersproject/base64": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" } }, "node_modules/@fortawesome/fontawesome-free": { - "version": "6.0.0-beta3", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.0.0-beta3.tgz", - "integrity": "sha512-4SqOuhC8tSLeQvbW1nDmq6T7+8vdSgHy/w7PRwCFzMQCbKuYFIir/3UuWsV1QblX1lt7SGlSgwbaCv9XhRt8HA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz", + "integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==", "hasInstallScript": true, "engines": { "node": ">=6" } }, - "node_modules/@gar/promisify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz", - "integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", - "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", + "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -2238,6 +2301,19 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -2340,20 +2416,20 @@ } }, "node_modules/@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", + "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/console/node_modules/ansi-styles": { @@ -2427,42 +2503,42 @@ } }, "node_modules/@jest/core": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", + "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", "dev": true, "dependencies": { - "@jest/console": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/reporters": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.8.1", + "ci-info": "^3.2.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.2.0", + "jest-config": "^29.3.1", + "jest-haste-map": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-regex-util": "^29.2.0", + "jest-resolve": "^29.3.1", + "jest-resolve-dependencies": "^29.3.1", + "jest-runner": "^29.3.1", + "jest-runtime": "^29.3.1", + "jest-snapshot": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", + "jest-watcher": "^29.3.1", "micromatch": "^4.0.4", - "rimraf": "^3.0.0", + "pretty-format": "^29.3.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -2544,85 +2620,110 @@ } }, "node_modules/@jest/environment": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", + "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", - "jest-mock": "^27.4.6" + "jest-mock": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", + "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", + "dev": true, + "dependencies": { + "expect": "^29.3.1", + "jest-snapshot": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", + "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.2.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", + "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "@sinonjs/fake-timers": "^8.0.1", + "@jest/types": "^29.3.1", + "@sinonjs/fake-timers": "^9.1.2", "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", + "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" + "@jest/environment": "^29.3.1", + "@jest/expect": "^29.3.1", + "@jest/types": "^29.3.1", + "jest-mock": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", + "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", + "@jridgewell/trace-mapping": "^0.3.15", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", + "jest-worker": "^29.3.1", "slash": "^3.0.0", - "source-map": "^0.6.0", "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -2691,104 +2792,128 @@ "node": ">=8" } }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/@jest/reporters/node_modules/jest-worker": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", + "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.3.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@jest/source-map": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" + "has-flag": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" } }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", + "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", + "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", "dev": true, "dependencies": { - "@jest/console": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/types": "^29.3.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", + "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", "dev": true, "dependencies": { - "@jest/test-result": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" + "@jest/test-result": "^29.3.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", + "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", "dev": true, "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", + "@babel/core": "^7.11.6", + "@jest/types": "^29.3.1", + "@jridgewell/trace-mapping": "^0.3.15", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "jest-regex-util": "^29.2.0", + "jest-util": "^29.3.1", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "write-file-atomic": "^4.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/transform/node_modules/ansi-styles": { @@ -2840,6 +2965,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@jest/transform/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2849,15 +2980,6 @@ "node": ">=8" } }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@jest/transform/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2871,19 +2993,20 @@ } }, "node_modules/@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", + "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", "dev": true, "dependencies": { + "@jest/schemas": "^29.0.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/types/node_modules/ansi-styles": { @@ -2956,6 +3079,59 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@metamask/safe-event-emitter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz", @@ -2996,56 +3172,21 @@ "node": ">= 8" } }, - "node_modules/@npmcli/fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.0.0.tgz", - "integrity": "sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } + "node_modules/@sinclair/typebox": { + "version": "0.24.27", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.27.tgz", + "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==", + "dev": true }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "engines": { "node": ">=10" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@sinonjs/commons": { @@ -3058,29 +3199,29 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, "dependencies": { "@sinonjs/commons": "^1.7.0" } }, "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dependencies": { - "defer-to-connect": "^1.0.1" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=14.16" } }, "node_modules/@tarekraafat/autocomplete.js": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@tarekraafat/autocomplete.js/-/autocomplete.js-10.2.6.tgz", - "integrity": "sha512-M3awL75YTQVHev4XlWGRKJalwVG3MGgtNnbRdtNipcyMcAV1sfkireBM8BKL/vhFhrus+6/5KZfj/DJDu8X0mg==", + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/@tarekraafat/autocomplete.js/-/autocomplete.js-10.2.7.tgz", + "integrity": "sha512-iE+dnXI8/LrTaSORrnNdSyXg/bFCbCpz/R5GUdB3ioW+9PVEhglxNcSDQNeCXtrbRG0kOBFUd4unEiwcmqyn8A==", "funding": [ { "type": "opencollective", @@ -3096,15 +3237,6 @@ } ] }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -3115,9 +3247,9 @@ } }, "node_modules/@types/babel__core": { - "version": "7.1.18", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", - "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", + "version": "7.1.20", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", + "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -3147,9 +3279,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", + "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", "dev": true, "dependencies": { "@babel/types": "^7.3.0" @@ -3163,6 +3295,17 @@ "@types/node": "*" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -3198,6 +3341,11 @@ "@types/node": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3222,6 +3370,17 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -3234,27 +3393,19 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true, - "optional": true, - "peer": true + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/node": { "version": "16.11.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==" }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3270,11 +3421,19 @@ } }, "node_modules/@types/prettier": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz", - "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", "dev": true }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", @@ -3289,28 +3448,34 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, "node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", + "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", "dev": true, "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, "node_modules/@walletconnect/browser-utils": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.7.8.tgz", - "integrity": "sha512-iCL0XCWOZaABIc0lqA79Vyaybr3z26nt8mxiwvfrG8oaKUf5Y21Of4dj+wIXQ4Hhblre6SgDlU0Ffb39+1THOw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.8.0.tgz", + "integrity": "sha512-Wcqqx+wjxIo9fv6eBUFHPsW1y/bGWWRboni5dfD8PtOmrihrEpOCmvRJe4rfl7xgJW8Ea9UqKEaq0bIRLHlK4A==", "dependencies": { "@walletconnect/safe-json": "1.0.0", - "@walletconnect/types": "^1.7.8", + "@walletconnect/types": "^1.8.0", "@walletconnect/window-getters": "1.0.0", "@walletconnect/window-metadata": "1.0.0", "detect-browser": "5.2.0" @@ -3322,24 +3487,24 @@ "integrity": "sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA==" }, "node_modules/@walletconnect/client": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.7.8.tgz", - "integrity": "sha512-pBroM6jZAaUM0SoXJZg5U7aPTiU3ljQAw3Xh/i2pxFDeN/oPKao7husZ5rdxS5xuGSV6YpqqRb0RxW1IeoR2Pg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.8.0.tgz", + "integrity": "sha512-svyBQ14NHx6Cs2j4TpkQaBI/2AF4+LXz64FojTjMtV4VMMhl81jSO1vNeg+yYhQzvjcGH/GpSwixjyCW0xFBOQ==", "dependencies": { - "@walletconnect/core": "^1.7.8", - "@walletconnect/iso-crypto": "^1.7.8", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8" + "@walletconnect/core": "^1.8.0", + "@walletconnect/iso-crypto": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" } }, "node_modules/@walletconnect/core": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.7.8.tgz", - "integrity": "sha512-9xcQ0YNf9JUFb0YOX1Mpy4Yojt+6w2yQz/0aIEyj2X/s9D71NW0fTYsMcdhkLOI7mn2cqVbx2t1lRvdgqsbrSQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.8.0.tgz", + "integrity": "sha512-aFTHvEEbXcZ8XdWBw6rpQDte41Rxwnuk3SgTD8/iKGSRTni50gI9S3YEzMj05jozSiOBxQci4pJDMVhIUMtarw==", "dependencies": { - "@walletconnect/socket-transport": "^1.7.8", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8" + "@walletconnect/socket-transport": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" } }, "node_modules/@walletconnect/crypto": { @@ -3369,12 +3534,12 @@ "integrity": "sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ==" }, "node_modules/@walletconnect/http-connection": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/http-connection/-/http-connection-1.7.8.tgz", - "integrity": "sha512-31gjBw46MRU9hFMTNXAqh+f8qpDNzVeV9BJehzVWKiNC3ciL1JCZkbvsY0djwajduE6TB2ujaML0XDXS9HgBRA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/http-connection/-/http-connection-1.8.0.tgz", + "integrity": "sha512-IziEr3c53qsMromK7jz0EkbKDHlryRbxXdFR+xaG+S5nfxtUdAfjzlZabvczXdDCgmTij6KbNsZAjBMqCBzACw==", "dependencies": { - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0", "eventemitter3": "4.0.7", "xhr2-cookies": "1.1.0" } @@ -3385,30 +3550,30 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/@walletconnect/iso-crypto": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.7.8.tgz", - "integrity": "sha512-Qo6qDcMG0Ac+9fpWE0h/oE55NHLk6eM2vlXpWlQDN/me7RZGrkvk+LXsAkQ3UiYPEiPfq4eswcyRWC9AcrAscg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.8.0.tgz", + "integrity": "sha512-pWy19KCyitpfXb70hA73r9FcvklS+FvO9QUIttp3c2mfW8frxgYeRXfxLRCIQTkaYueRKvdqPjbyhPLam508XQ==", "dependencies": { "@walletconnect/crypto": "^1.0.2", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8" + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" } }, "node_modules/@walletconnect/jsonrpc-types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.0.tgz", - "integrity": "sha512-11QXNq5H1PKZk7bP8SxgmCw3HRaDuPOVE+wObqEvmhc7OWYUZqfuaaMb+OXGRSOHL3sbC+XHfdeCxFTMXSFyng==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.1.tgz", + "integrity": "sha512-+6coTtOuChCqM+AoYyi4Q83p9l/laI6NvuM2/AHaZFuf0gT0NjW7IX2+86qGyizn7Ptq4AYZmfxurAxTnhefuw==", "dependencies": { "keyvaluestorage-interface": "^1.0.0" } }, "node_modules/@walletconnect/jsonrpc-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.0.tgz", - "integrity": "sha512-qUHbKUK6sHeHn67qtHZoLoYk5hS6x1arTPjKDRkY93/6Fx+ZmNIpdm1owX3l6aYueyegJ7mz43FpvYHUqJ8xcw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.3.tgz", + "integrity": "sha512-3yb49bPk16MNLk6uIIHPSHQCpD6UAo1OMOx1rM8cW/MPEAYAzrSW5hkhG7NEUwX9SokRIgnZK3QuQkiyNzBMhQ==", "dependencies": { "@walletconnect/environment": "^1.0.0", - "@walletconnect/jsonrpc-types": "^1.0.0" + "@walletconnect/jsonrpc-types": "^1.0.1" } }, "node_modules/@walletconnect/mobile-registry": { @@ -3418,13 +3583,13 @@ "deprecated": "Deprecated in favor of dynamic registry available from: https://github.com/walletconnect/walletconnect-registry" }, "node_modules/@walletconnect/qrcode-modal": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.7.8.tgz", - "integrity": "sha512-LqNJMLWO+ljvoRSdq8tcEslW0imKrrb+ugs3bw4w/jEI1FSJzVeinEsgVpyaMv8wsUcyTcSCXSkXpT1SXHtcpw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.8.0.tgz", + "integrity": "sha512-BueaFefaAi8mawE45eUtztg3ZFbsAH4DDXh1UNwdUlsvFMjqcYzLUG0xZvDd6z2eOpbgDg2N3bl6gF0KONj1dg==", "dependencies": { - "@walletconnect/browser-utils": "^1.7.8", + "@walletconnect/browser-utils": "^1.8.0", "@walletconnect/mobile-registry": "^1.4.0", - "@walletconnect/types": "^1.7.8", + "@walletconnect/types": "^1.8.0", "copy-to-clipboard": "^3.3.1", "preact": "10.4.1", "qrcode": "1.4.4" @@ -3446,12 +3611,12 @@ "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg==" }, "node_modules/@walletconnect/socket-transport": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.7.8.tgz", - "integrity": "sha512-bqEjLxfSzG79v2OT7XVOZoyUkg6g3yng0fURrdLusWs42fYHWnrSrIZDejFn8N5PiZk5R2edrggkQ7w0VUUAfw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.8.0.tgz", + "integrity": "sha512-5DyIyWrzHXTcVp0Vd93zJ5XMW61iDM6bcWT4p8DTRfFsOtW46JquruMhxOLeCOieM4D73kcr3U7WtyR4JUsGuQ==", "dependencies": { - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0", "ws": "7.5.3" } }, @@ -3476,19 +3641,19 @@ } }, "node_modules/@walletconnect/types": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.7.8.tgz", - "integrity": "sha512-0oSZhKIrtXRJVP1jQ0EDTRtotQY6kggGjDcmm/LLQBKnOZXdPeo0sPkV/7DjT5plT3O7Cjc6JvuXt9WOY0hlCA==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.8.0.tgz", + "integrity": "sha512-Cn+3I0V0vT9ghMuzh1KzZvCkiAxTq+1TR2eSqw5E5AVWfmCtECFkVZBP6uUJZ8YjwLqXheI+rnjqPy7sVM4Fyg==" }, "node_modules/@walletconnect/utils": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.7.8.tgz", - "integrity": "sha512-DSpfH6Do0TQmdrgzu+SyjVhupVjN0WEMvNWGK9K4VlSmLFpCWfme7qxzrvuxBZ47gDqs1kGWvjyJmviWqvOnAg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.8.0.tgz", + "integrity": "sha512-zExzp8Mj1YiAIBfKNm5u622oNw44WOESzo6hj+Q3apSMIb0Jph9X3GDIdbZmvVZsNPxWDL7uodKgZcCInZv2vA==", "dependencies": { - "@walletconnect/browser-utils": "^1.7.8", + "@walletconnect/browser-utils": "^1.8.0", "@walletconnect/encoding": "^1.0.1", - "@walletconnect/jsonrpc-utils": "^1.0.0", - "@walletconnect/types": "^1.7.8", + "@walletconnect/jsonrpc-utils": "^1.0.3", + "@walletconnect/types": "^1.8.0", "bn.js": "4.11.8", "js-sha3": "0.8.0", "query-string": "6.13.5" @@ -3518,21 +3683,21 @@ "node_modules/@walletconnect/utils/node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", "engines": { "node": ">=4" } }, "node_modules/@walletconnect/web3-provider": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/web3-provider/-/web3-provider-1.7.8.tgz", - "integrity": "sha512-2VxGo7KPfQTWRJ+rygt3ok/u04InkVE+H9LBIF/RMUwcwyGf2nsP3CcYZVcg3yYpacgN7bAZCersCEYwU8AeeA==", - "dependencies": { - "@walletconnect/client": "^1.7.8", - "@walletconnect/http-connection": "^1.7.8", - "@walletconnect/qrcode-modal": "^1.7.8", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/web3-provider/-/web3-provider-1.8.0.tgz", + "integrity": "sha512-lqqEO0oRmCehH+c8ZPk3iH7I7YtbzmkWd58/Or2AgWAl869JamzndKCD3sTlNsPRQLxxPpraHQqzur7uclLWvg==", + "dependencies": { + "@walletconnect/client": "^1.8.0", + "@walletconnect/http-connection": "^1.8.0", + "@walletconnect/qrcode-modal": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0", "web3-provider-engine": "16.0.1" } }, @@ -3696,34 +3861,42 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", "dev": true, - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -3744,18 +3917,15 @@ "dev": true }, "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "optional": true, - "peer": true + "node_modules/abortcontroller-polyfill": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", + "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" }, "node_modules/abstract-leveldown": { "version": "2.6.3", @@ -3766,21 +3936,21 @@ } }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3799,6 +3969,18 @@ "acorn-walk": "^7.1.1" } }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -3834,37 +4016,6 @@ "node": ">= 6.0.0" } }, - "node_modules/agentkeepalive": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.0.tgz", - "integrity": "sha512-0PhAp58jZNw13UJv7NVdTGb0ZcghHUb3DrZ046JiiJY/BOaTTpbwdHq2VObPCBV8M2GPh7sgrJ3AQ8Ey468LJw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3928,12 +4079,6 @@ "ajv": "^6.9.1" } }, - "node_modules/alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -3982,29 +4127,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -4017,7 +4139,7 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/array-includes": { "version": "3.1.4", @@ -4038,18 +4160,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -4067,17 +4177,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -4145,17 +4244,6 @@ "async": "^2.4.0" } }, - "node_modules/async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -4180,14 +4268,24 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], "dependencies": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -4198,10 +4296,6 @@ "engines": { "node": "^10 || ^12 || >=14" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": "^8.1.0" } @@ -4231,22 +4325,21 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "node_modules/babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", + "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", "dev": true, "dependencies": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/transform": "^29.3.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", + "babel-preset-jest": "^29.2.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" @@ -4323,31 +4416,20 @@ } }, "node_modules/babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", + "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", "dev": true, "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 8.9" + "node": ">= 14.15.0" }, "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" + "@babel/core": "^7.12.0", + "webpack": ">=5" } }, "node_modules/babel-plugin-istanbul": { @@ -4367,27 +4449,27 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", + "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", "dependencies": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", "semver": "^6.1.1" }, "peerDependencies": { @@ -4395,13 +4477,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.1.tgz", - "integrity": "sha512-TihqEe4sQcb/QcPJvxe94/9RZuLQuF1+To4WqQcRvc+3J3gLCPIPgDKzGLG6zmQLfH3nn25heRuDNkS2KR4I8A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.20.0" + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -4461,16 +4543,16 @@ } }, "node_modules/babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", + "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^27.4.0", + "babel-plugin-jest-hoist": "^29.2.0", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -4538,9 +4620,9 @@ } }, "node_modules/bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "engines": { "node": "*" } @@ -4565,28 +4647,31 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "node_modules/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dependencies": { - "bytes": "3.1.1", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -4597,15 +4682,26 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/body-parser/node_modules/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" }, @@ -4616,7 +4712,7 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, "node_modules/bootstrap": { @@ -4744,25 +4840,30 @@ ] }, "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/bs58": { @@ -4843,7 +4944,7 @@ "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -4853,7 +4954,7 @@ "node_modules/buffer-to-arraybuffer": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", - "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=" + "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==" }, "node_modules/buffer-xor": { "version": "1.0.3", @@ -4904,56 +5005,33 @@ } }, "node_modules/bytes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, + "node_modules/cacheable-lookup": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", + "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==", "engines": { - "node": ">= 10" + "node": ">=10.6.0" } }, "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", + "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" }, "engines": { "node": ">=8" @@ -4981,14 +5059,6 @@ "node": ">=8" } }, - "node_modules/cacheable-request/node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -5018,25 +5088,6 @@ "node": ">=6" } }, - "node_modules/camelcase-keys": { - "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, - "optional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", @@ -5055,13 +5106,19 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001303", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz", - "integrity": "sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "version": "1.0.30001426", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001426.tgz", + "integrity": "sha512-n7cosrHLl8AWt0wwZw/PJZgUg3lV0gk9LMI7ikGJwhyhgsd2Nb65vKvmSexCqq/J7rbH3mFG6yZZiPR5dLPW5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/caseless": { "version": "0.12.0", @@ -5107,17 +5164,20 @@ } }, "node_modules/chart.js": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.0.tgz", - "integrity": "sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.0.1.tgz", + "integrity": "sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==", + "engines": { + "pnpm": "^7.0.0" + } }, "node_modules/chartjs-adapter-luxon": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.1.0.tgz", - "integrity": "sha512-CS+xBWEyXYVLBZ3dSY/MwlSXhz8er4JjkApazY84ft/++oOLsmkt6TaXBCsUFudum7QdoYmpxiL/gSp20+emkw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.0.tgz", + "integrity": "sha512-TPqS8S7aw4a07LhFzG5DZU6Kduk1xFkaGTn8y/gfhBRvfyCkqnwFqfXqG9Gl+gmnj5DRXgPscApJUE6bsgzKjQ==", "peerDependencies": { - "chart.js": "^3.0.0", - "luxon": "^1.0.0 || ^2.0.0" + "chart.js": ">=3.0.0", + "luxon": ">=1.0.0" } }, "node_modules/check-error": { @@ -5175,17 +5235,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -5196,9 +5245,9 @@ } }, "node_modules/ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", "dev": true }, "node_modules/cids": { @@ -5248,21 +5297,10 @@ "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==" }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/clipboard": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.9.tgz", - "integrity": "sha512-JK1AIxL35mdvQoP50gelg1Gsx6wMsfjtMCzFAdePdrGEHaqTnNAz3qZg4vhmLQDlpTqiVgj7GRzmpIDsAcdvRA==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", "dependencies": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -5303,17 +5341,20 @@ } }, "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dependencies": { "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "engines": { "iojs": ">= 1.0.0", @@ -5339,21 +5380,10 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/colord": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.1.tgz", - "integrity": "sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", + "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", "dev": true }, "node_modules/colorette": { @@ -5394,14 +5424,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5459,9 +5481,9 @@ } }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -5469,7 +5491,7 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/cookiejar": { "version": "2.1.3", @@ -5477,28 +5499,28 @@ "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, "node_modules/copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", "dependencies": { "toggle-selection": "^1.0.6" } }, "node_modules/copy-webpack-plugin": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.1.tgz", - "integrity": "sha512-nr81NhCAIpAWXGCK5thrKmfCQ6GDY0L5RN0U+BnIn/7Us55+UCex5ANNsNKmIVtDRnk0Ecf+/kzp9SUVrrBMLg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", "dev": true, "dependencies": { - "fast-glob": "^3.2.7", + "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", - "globby": "^12.0.2", + "globby": "^13.1.1", "normalize-path": "^3.0.0", "schema-utils": "^4.0.0", "serialize-javascript": "^6.0.0" }, "engines": { - "node": ">= 12.20.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", @@ -5508,63 +5530,10 @@ "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/core-js": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz", - "integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==", + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", + "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5572,26 +5541,17 @@ } }, "node_modules/core-js-compat": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.3.tgz", - "integrity": "sha512-c8M5h0IkNZ+I92QhIpuSijOxGAcj3lgpsWdkCqmUTZNwidujF4r3pi6x1DCN+Vcs5qTS2XWWMfWSuCqyupX8gw==", + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.1.tgz", + "integrity": "sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw==", "dependencies": { - "browserslist": "^4.19.1", - "semver": "7.0.0" + "browserslist": "^4.21.3" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -5626,13 +5586,9 @@ } }, "node_modules/crc-32": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", - "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.3.1" - }, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "bin": { "crc32": "bin/crc32.njs" }, @@ -5731,25 +5687,13 @@ "node": ">=4" } }, - "node_modules/css-color-names": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", - "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/css-declaration-sorter": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz", - "integrity": "sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", + "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", "dev": true, - "dependencies": { - "timsort": "^0.3.0" - }, "engines": { - "node": ">= 10" + "node": "^10 || ^12 || >=14" }, "peerDependencies": { "postcss": "^8.0.9" @@ -5783,20 +5727,6 @@ "webpack": "^4.27.0 || ^5.0.0" } }, - "node_modules/css-loader/node_modules/loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/css-loader/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -5831,20 +5761,20 @@ } }, "node_modules/css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz", + "integrity": "sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==", "dev": true, "dependencies": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", + "cssnano": "^5.1.8", + "jest-worker": "^29.1.2", + "postcss": "^8.4.17", "schema-utils": "^4.0.0", "serialize-javascript": "^6.0.0", "source-map": "^0.6.1" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", @@ -5857,6 +5787,9 @@ "@parcel/css": { "optional": true }, + "@swc/css": { + "optional": true + }, "clean-css": { "optional": true }, @@ -5865,82 +5798,62 @@ }, "esbuild": { "optional": true + }, + "lightningcss": { + "optional": true } } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "node_modules/css-minimizer-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=8" } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/css-minimizer-webpack-plugin/node_modules/jest-worker": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.1.2.tgz", + "integrity": "sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.3" + "@types/node": "*", + "jest-util": "^29.1.2", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, - "peerDependencies": { - "ajv": "^8.8.2" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "node_modules/css-minimizer-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "dev": true, "dependencies": { "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" }, "funding": { "url": "https://github.com/sponsors/fb55" @@ -5969,19 +5882,10 @@ "node": ">=8.0.0" } }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/css-what": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", - "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true, "engines": { "node": ">= 6" @@ -6008,13 +5912,12 @@ "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" }, "node_modules/cssnano": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.0.9.tgz", - "integrity": "sha512-Y4olTKBKsPKl5izpcXHRDiB/1rVdbIDM4qVXgEKBt466kYT42SEEsnCYOQFFXzEkUYV8pJNCII9JKzb8KfDk+g==", + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.12.tgz", + "integrity": "sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ==", "dev": true, "dependencies": { - "cssnano-preset-default": "^5.1.5", - "is-resolvable": "^1.1.0", + "cssnano-preset-default": "^5.2.12", "lilconfig": "^2.0.3", "yaml": "^1.10.2" }, @@ -6030,40 +5933,40 @@ } }, "node_modules/cssnano-preset-default": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.5.tgz", - "integrity": "sha512-fF00UI+d3PWkGfMd62geqmoUe5h+LOhGE2GH4Fqq3beNKdCU1LWwLUyIcu4/A72lWv0737cHey5zhhWw3rW0sA==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^6.0.3", - "cssnano-utils": "^2.0.1", - "postcss-calc": "^8.0.0", - "postcss-colormin": "^5.2.1", - "postcss-convert-values": "^5.0.2", - "postcss-discard-comments": "^5.0.1", - "postcss-discard-duplicates": "^5.0.1", - "postcss-discard-empty": "^5.0.1", - "postcss-discard-overridden": "^5.0.1", - "postcss-merge-longhand": "^5.0.2", - "postcss-merge-rules": "^5.0.2", - "postcss-minify-font-values": "^5.0.1", - "postcss-minify-gradients": "^5.0.3", - "postcss-minify-params": "^5.0.1", - "postcss-minify-selectors": "^5.1.0", - "postcss-normalize-charset": "^5.0.1", - "postcss-normalize-display-values": "^5.0.1", - "postcss-normalize-positions": "^5.0.1", - "postcss-normalize-repeat-style": "^5.0.1", - "postcss-normalize-string": "^5.0.1", - "postcss-normalize-timing-functions": "^5.0.1", - "postcss-normalize-unicode": "^5.0.1", - "postcss-normalize-url": "^5.0.2", - "postcss-normalize-whitespace": "^5.0.1", - "postcss-ordered-values": "^5.0.2", - "postcss-reduce-initial": "^5.0.1", - "postcss-reduce-transforms": "^5.0.1", - "postcss-svgo": "^5.0.3", - "postcss-unique-selectors": "^5.0.1" + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", + "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^6.3.0", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.0", + "postcss-convert-values": "^5.1.2", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.6", + "postcss-merge-rules": "^5.1.2", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.3", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -6073,9 +5976,9 @@ } }, "node_modules/cssnano-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", - "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", "dev": true, "engines": { "node": "^10 || ^12 || >=14.0" @@ -6097,9 +6000,9 @@ } }, "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true }, "node_modules/cssstyle": { @@ -6141,17 +6044,17 @@ } }, "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "dev": true, "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/debug": { @@ -6178,61 +6081,49 @@ "node": ">=0.10.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", "dev": true }, "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "engines": { "node": ">=0.10" } }, "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dependencies": { - "mimic-response": "^1.0.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, "node_modules/deep-eql": { @@ -6262,9 +6153,12 @@ } }, "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } }, "node_modules/deferred-leveldown": { "version": "1.2.2", @@ -6298,22 +6192,6 @@ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -6324,9 +6202,13 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-browser": { "version": "5.2.1", @@ -6343,12 +6225,12 @@ } }, "node_modules/diff-sequences": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", + "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/diffie-hellman": { @@ -6396,9 +6278,9 @@ } }, "node_modules/dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", "dev": true, "dependencies": { "domelementtype": "^2.0.1", @@ -6415,9 +6297,9 @@ "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true, "funding": [ { @@ -6427,30 +6309,21 @@ ] }, "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "dev": true, "dependencies": { - "webidl-conversions": "^5.0.0" + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", "dev": true, "dependencies": { "domelementtype": "^2.2.0" @@ -6481,11 +6354,6 @@ "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", "integrity": "sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA==" }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -6498,12 +6366,12 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.55", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.55.tgz", - "integrity": "sha512-AoCDEVElLY8mwe4TuDDkr1jxvSh/Ih5PFlEXCpmwFkq9JOXn4K58CScgBl+R1ghFW9cPJ7VeWo30nAHSRCe6rw==" + "version": "1.4.256", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", + "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -6525,12 +6393,12 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sindresorhus/emittery?sponsor=1" @@ -6554,7 +6422,7 @@ "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } @@ -6591,9 +6459,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -6612,17 +6480,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -6635,14 +6492,6 @@ "node": ">=4" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -6719,19 +6568,23 @@ } }, "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "dependencies": { "d": "1", "es5-ext": "^0.10.35", @@ -6743,6 +6596,11 @@ "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "node_modules/es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -6763,12 +6621,12 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { "node": ">=0.8.0" } @@ -6807,7 +6665,7 @@ "node_modules/escodegen/node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, "dependencies": { "prelude-ls": "~1.1.2", @@ -6837,26 +6695,16 @@ "node_modules/escodegen/node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true, "engines": { "node": ">= 0.8.0" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/escodegen/node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, "dependencies": { "prelude-ls": "~1.1.2" @@ -6866,13 +6714,15 @@ } }, "node_modules/eslint": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz", - "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", + "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.9.2", + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -6882,18 +6732,21 @@ "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", + "espree": "^9.4.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -6904,8 +6757,7 @@ "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -7194,9 +7046,9 @@ } }, "node_modules/eslint-plugin-promise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", - "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7367,6 +7219,22 @@ "node": ">=4.0" } }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/globals": { "version": "13.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", @@ -7403,6 +7271,21 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7415,6 +7298,30 @@ "node": "*" } }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/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, + "engines": { + "node": ">=8" + } + }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7440,29 +7347,20 @@ } }, "node_modules/espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", "dev": true, "dependencies": { - "acorn": "^8.7.1", + "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" }, - "engines": { - "node": ">=0.4.0" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -7541,7 +7439,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } @@ -7562,7 +7460,7 @@ "node_modules/eth-ens-namehash": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", - "integrity": "sha1-IprEbsqG1S4MmR58sq74P/D2i88=", + "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", "dependencies": { "idna-uts46-hx": "^2.3.1", "js-sha3": "^0.5.7" @@ -7571,7 +7469,7 @@ "node_modules/eth-ens-namehash/node_modules/js-sha3": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==" }, "node_modules/eth-json-rpc-filters": { "version": "4.2.2", @@ -7692,9 +7590,9 @@ } }, "node_modules/eth-net-props": { - "version": "1.0.40", - "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.40.tgz", - "integrity": "sha512-HSZQ+lmEqSTV9B68gjoBHL5seJsahiDyvlbXtG601+BzXhUIxVwfkBvhjbw5LQgKDBlDwwPryBqfKXAAU3rXPA==", + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.41.tgz", + "integrity": "sha512-4qUNJU8xyqV53Lr+5JMnCUoknL/IIQ8Zpk1CKV/8h7tvtdrqbvnvJNb3IC0nUcyf4f0tNRTQnSWslut7XRwpRA==", "dependencies": { "chai": "^4.2.0" } @@ -7910,9 +7808,9 @@ } }, "node_modules/ethereumjs-util": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.3.tgz", - "integrity": "sha512-y+82tEbyASO0K0X1/SRhbJJoAlfcvq8JbrG4a5cjrOks7HS/36efU/0j2flxCPOUM++HFahk33kr/ZxyC4vNuw==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dependencies": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", @@ -7925,9 +7823,9 @@ } }, "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", - "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", "dependencies": { "@types/node": "*" } @@ -8010,7 +7908,7 @@ "node_modules/ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk=", + "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", "dependencies": { "bn.js": "4.11.6", "number-to-bn": "1.7.0" @@ -8023,7 +7921,7 @@ "node_modules/ethjs-unit/node_modules/bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" }, "node_modules/ethjs-util": { "version": "0.1.6", @@ -8083,82 +7981,64 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "engines": { "node": ">= 0.8.0" } }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/expect": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", + "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" + "@jest/expect-utils": "^29.3.1", + "jest-get-type": "^29.2.0", + "jest-matcher-utils": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.1", + "body-parser": "1.20.1", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.1", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.6", + "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -8175,15 +8055,26 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" }, @@ -8211,17 +8102,17 @@ ] }, "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", "dependencies": { - "type": "^2.5.0" + "type": "^2.7.2" } }, "node_modules/ext/node_modules/type": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", - "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, "node_modules/extend": { "version": "3.0.2", @@ -8309,9 +8200,9 @@ } }, "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "dependencies": { "bser": "2.1.1" @@ -8349,20 +8240,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/file-loader/node_modules/loader-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.1.tgz", - "integrity": "sha512-g4miPa9uUrZz4iElkaVJgDFwKJGh8aQGM7pUL4ejXl6cu7kSb30seQOVGNMP6sW8j7DW77X68hJZ+GM7UGhXeQ==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/file-loader/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -8394,16 +8271,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -8421,7 +8298,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-cache-dir": { "version": "3.3.2", @@ -8570,6 +8447,11 @@ "node": ">= 0.12" } }, + "node_modules/form-data-encoder": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz", + "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8594,7 +8476,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } @@ -8609,20 +8491,6 @@ "universalify": "^0.1.0" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8653,42 +8521,6 @@ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8735,26 +8567,15 @@ "node": ">=8.0.0" } }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { @@ -8836,15 +8657,14 @@ } }, "node_modules/globby": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", - "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", "dev": true, "dependencies": { - "array-union": "^3.0.1", "dir-glob": "^3.0.1", - "fast-glob": "^3.2.7", - "ignore": "^5.1.9", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^4.0.0" }, @@ -8867,44 +8687,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz", - "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/good-listener": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", @@ -8914,24 +8696,29 @@ } }, "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz", + "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "@szmarczak/http-timer": "^5.0.1", + "@types/cacheable-request": "^6.0.2", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^6.0.4", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "form-data-encoder": "1.7.1", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=14.16" }, - "engines": { - "node": ">=8.6" + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, "node_modules/graceful-fs": { @@ -8939,6 +8726,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -8960,17 +8753,6 @@ "node": ">=6" } }, - "node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8998,14 +8780,6 @@ "node": ">=4" } }, - "node_modules/has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "engines": { - "node": "*" - } - }, "node_modules/has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", @@ -9017,17 +8791,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "dependencies": { - "has-symbol-support-x": "^1.4.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", @@ -9042,14 +8805,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -9092,9 +8847,9 @@ } }, "node_modules/highlight.js": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.4.0.tgz", - "integrity": "sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==", "engines": { "node": ">=12.0.0" } @@ -9117,24 +8872,16 @@ "react-is": "^16.7.0" } }, - "node_modules/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, - "optional": true, - "peer": true - }, "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, "dependencies": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/html-escaper": { @@ -9149,38 +8896,32 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" } }, "node_modules/http-https": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", - "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } + "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==" }, "node_modules/http-signature": { "version": "1.2.0", @@ -9196,15 +8937,38 @@ "npm": ">=1.3.7" } }, + "node_modules/http2-wrapper": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz", + "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "dependencies": { "agent-base": "6", @@ -9223,17 +8987,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/humps": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", @@ -9276,7 +9029,7 @@ "node_modules/idna-uts46-hx/node_modules/punycode": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", "engines": { "node": ">=6" } @@ -9434,25 +9187,6 @@ "node": ">=0.8.19" } }, - "node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -9482,22 +9216,14 @@ } }, "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -9506,15 +9232,6 @@ "node": ">= 0.10" } }, - "node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -9685,14 +9402,6 @@ "npm": ">=3" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", @@ -9742,20 +9451,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/is-plain-object": { @@ -9791,20 +9493,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "node_modules/is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-shared-array-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", @@ -9922,9 +9610,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", @@ -9986,19 +9674,10 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -10008,33 +9687,22 @@ "node": ">=8" } }, - "node_modules/isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dependencies": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", + "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", "dev": true, "dependencies": { - "@jest/core": "^27.4.7", + "@jest/core": "^29.3.1", + "@jest/types": "^29.3.1", "import-local": "^3.0.2", - "jest-cli": "^27.4.7" + "jest-cli": "^29.3.1" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -10046,47 +9714,46 @@ } }, "node_modules/jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", + "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", "execa": "^5.0.0", - "throat": "^6.0.1" + "p-limit": "^3.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", + "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^29.3.1", + "@jest/expect": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.4.6", "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", + "jest-each": "^29.3.1", + "jest-matcher-utils": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-runtime": "^29.3.1", + "jest-snapshot": "^29.3.1", + "jest-util": "^29.3.1", + "p-limit": "^3.1.0", + "pretty-format": "^29.3.1", "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" + "stack-utils": "^2.0.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus/node_modules/ansi-styles": { @@ -10160,29 +9827,29 @@ } }, "node_modules/jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", + "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", "dev": true, "dependencies": { - "@jest/core": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/core": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/types": "^29.3.1", "chalk": "^4.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-config": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", "prompts": "^2.0.1", - "yargs": "^16.2.0" + "yargs": "^17.3.1" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -10264,41 +9931,45 @@ } }, "node_modules/jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", + "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", "dev": true, "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.3.1", + "@jest/types": "^29.3.1", + "babel-jest": "^29.3.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.3.1", + "jest-environment-node": "^29.3.1", + "jest-get-type": "^29.2.0", + "jest-regex-util": "^29.2.0", + "jest-resolve": "^29.3.1", + "jest-runner": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" + "parse-json": "^5.2.0", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { + "@types/node": "*", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, "ts-node": { "optional": true } @@ -10375,18 +10046,18 @@ } }, "node_modules/jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", + "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "diff-sequences": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-diff/node_modules/ansi-styles": { @@ -10460,31 +10131,31 @@ } }, "node_modules/jest-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", + "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", + "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" + "jest-get-type": "^29.2.0", + "jest-util": "^29.3.1", + "pretty-format": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-each/node_modules/ansi-styles": { @@ -10558,199 +10229,148 @@ } }, "node_modules/jest-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz", + "integrity": "sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", - "jsdom": "^16.6.0" + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1", + "jsdom": "^20.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, "node_modules/jest-environment-node": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", + "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", + "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", + "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "@types/graceful-fs": "^4.1.2", + "@jest/types": "^29.3.1", + "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.2.0", + "jest-util": "^29.3.1", + "jest-worker": "^29.3.1", "micromatch": "^4.0.4", - "walker": "^1.0.7" + "walker": "^1.0.8" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, - "node_modules/jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", + "node_modules/jest-haste-map/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "throat": "^6.0.1" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" } }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-haste-map/node_modules/jest-worker": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", + "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@types/node": "*", + "jest-util": "^29.3.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-haste-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/jest-leak-detector": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", + "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", "dev": true, "dependencies": { - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", + "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-diff": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils/node_modules/ansi-styles": { @@ -10824,23 +10444,23 @@ } }, "node_modules/jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", + "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", + "pretty-format": "^29.3.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util/node_modules/ansi-styles": { @@ -10914,16 +10534,17 @@ } }, "node_modules/jest-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", + "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "@types/node": "*" + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-util": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -10944,47 +10565,45 @@ } }, "node_modules/jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", + "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", + "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", + "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" + "jest-regex-util": "^29.2.0", + "jest-snapshot": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve/node_modules/ansi-styles": { @@ -11058,36 +10677,35 @@ } }, "node_modules/jest-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", + "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", "dev": true, "dependencies": { - "@jest/console": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/environment": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.2.0", + "jest-environment-node": "^29.3.1", + "jest-haste-map": "^29.3.1", + "jest-leak-detector": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-resolve": "^29.3.1", + "jest-runtime": "^29.3.1", + "jest-util": "^29.3.1", + "jest-watcher": "^29.3.1", + "jest-worker": "^29.3.1", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner/node_modules/ansi-styles": { @@ -11148,6 +10766,46 @@ "node": ">=8" } }, + "node_modules/jest-runner/node_modules/jest-worker": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", + "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.3.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/jest-runner/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11161,36 +10819,36 @@ } }, "node_modules/jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", + "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/globals": "^29.3.1", + "@jest/source-map": "^29.2.0", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-regex-util": "^29.2.0", + "jest-resolve": "^29.3.1", + "jest-snapshot": "^29.3.1", + "jest-util": "^29.3.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runtime/node_modules/ansi-styles": { @@ -11263,50 +10921,39 @@ "node": ">=8" } }, - "node_modules/jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", + "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", "dev": true, "dependencies": { - "@babel/core": "^7.7.2", + "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__traverse": "^7.0.4", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/babel__traverse": "^7.0.6", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "expect": "^29.3.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.3.1", + "jest-get-type": "^29.2.0", + "jest-haste-map": "^29.3.1", + "jest-matcher-utils": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", - "semver": "^7.3.2" + "pretty-format": "^29.3.1", + "semver": "^7.3.5" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-snapshot/node_modules/ansi-styles": { @@ -11368,9 +11015,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11395,20 +11042,20 @@ } }, "node_modules/jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", + "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util/node_modules/ansi-styles": { @@ -11482,20 +11129,20 @@ } }, "node_modules/jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", + "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", + "jest-get-type": "^29.2.0", "leven": "^3.1.0", - "pretty-format": "^27.4.6" + "pretty-format": "^29.3.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-validate/node_modules/ansi-styles": { @@ -11581,21 +11228,22 @@ } }, "node_modules/jest-watcher": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", + "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", "dev": true, "dependencies": { - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/test-result": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.4.2", + "emittery": "^0.13.1", + "jest-util": "^29.3.1", "string-length": "^4.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-watcher/node_modules/ansi-styles": { @@ -11669,9 +11317,9 @@ } }, "node_modules/jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "dependencies": { "@types/node": "*", @@ -11707,17 +11355,23 @@ } }, "node_modules/jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.2.tgz", + "integrity": "sha512-/e7ulNIEEYk1Z/l4X0vpxGt+B/dNsV8ghOPAWZaJs8pkGvsSC0tm33aMGylXcj/U7y4IcvwtMXPMyBFZn/gK9A==" }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true, - "optional": true, - "peer": true + "node_modules/js-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/js-sdsl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", + "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", + "dev": true }, "node_modules/js-sha3": { "version": "0.8.0", @@ -11748,41 +11402,41 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", "dev": true, "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", + "abab": "^2.0.6", + "acorn": "^8.7.1", "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", + "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "parse5": "^7.0.0", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.0.0", "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "peerDependencies": { "canvas": "^2.5.0" @@ -11793,22 +11447,19 @@ } } }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "node_modules/jsdom/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": ">= 10" } }, "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { "asynckit": "^0.4.0", @@ -11819,6 +11470,20 @@ "node": ">= 6" } }, + "node_modules/jsdom/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -11831,15 +11496,9 @@ } }, "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -11902,12 +11561,9 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dependencies": { - "minimist": "^1.2.5" - }, + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "bin": { "json5": "lib/cli.js" }, @@ -11918,7 +11574,7 @@ "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -11960,11 +11616,11 @@ } }, "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", "dependencies": { - "json-buffer": "3.0.0" + "json-buffer": "3.0.1" } }, "node_modules/keyvaluestorage-interface": { @@ -12135,9 +11791,9 @@ } }, "node_modules/lilconfig": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", - "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", "dev": true, "engines": { "node": ">=10" @@ -12159,29 +11815,17 @@ } }, "node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "json5": "^2.1.2" }, "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/loader-utils/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" + "node": ">=8.9.0" } }, "node_modules/locate-path": { @@ -12265,7 +11909,7 @@ "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, "node_modules/lodash.merge": { @@ -12301,7 +11945,7 @@ "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, "node_modules/loose-envify": { @@ -12316,11 +11960,14 @@ } }, "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lru-cache": { @@ -12341,9 +11988,9 @@ "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" }, "node_modules/luxon": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.4.0.tgz", - "integrity": "sha512-w+NAwWOUL5hO0SgwOHsMBAmZ15SoknmQXhSO0hIbJCAmPKSsGeK8MlmhYh2w6Iib38IxN2M+/ooXWLbeis7GuA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.1.tgz", + "integrity": "sha512-Ah6DloGmvseB/pX1cAmjbFvyU/pKuwQMQqz7d0yvuDlVYLTs2WeDHQMpC8tGjm1da+BriHROW/OEIT/KfYg6xw==", "engines": { "node": ">=12" } @@ -12363,35 +12010,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -12401,20 +12019,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -12434,7 +12038,7 @@ "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { "node": ">= 0.6" } @@ -12460,129 +12064,39 @@ "xtend": "~4.0.0" } }, - "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, - "node_modules/meow/node_modules/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==", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "engines": { - "node": ">=10" + "node": ">= 8" } }, - "node_modules/meow/node_modules/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, - "optional": true, - "peer": true, + "node_modules/merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/merkle-patricia-tree": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", - "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", - "dependencies": { - "async": "^1.4.2", - "ethereumjs-util": "^5.0.0", - "level-ws": "0.0.0", - "levelup": "^1.2.1", - "memdown": "^1.0.0", - "readable-stream": "^2.0.0", - "rlp": "^2.0.0", - "semaphore": ">=1.0.1" + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" } }, "node_modules/merkle-patricia-tree/node_modules/async": { @@ -12634,7 +12148,7 @@ "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "engines": { "node": ">= 0.6" } @@ -12681,19 +12195,19 @@ } }, "node_modules/mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.50.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -12724,21 +12238,10 @@ "dom-walk": "^0.1.0" } }, - "node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/mini-css-extract-plugin": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz", - "integrity": "sha512-YseMB8cs8U/KCaAGQoqYmfUuhhGW0a9p9XvWXrxVOkE3/IiISTLw4ALNt7JR5B2eYauFM+PQGSbXMDmVbR7Tfw==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", + "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", "dev": true, "dependencies": { "schema-utils": "^4.0.0" @@ -12754,59 +12257,6 @@ "webpack": "^5.0.0" } }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -12818,9 +12268,9 @@ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -12834,126 +12284,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, - "node_modules/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, - "optional": true, - "peer": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -12968,7 +12298,7 @@ "node_modules/mkdirp-promise": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", - "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=", + "integrity": "sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==", "deprecated": "This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that.", "dependencies": { "mkdirp": "*" @@ -12983,9 +12313,9 @@ "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, "node_modules/moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "engines": { "node": "*" } @@ -13034,18 +12364,10 @@ "buffer": "^5.5.0" } }, - "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/nano-json-stream-parser": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", - "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=" + "integrity": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==" }, "node_modules/nanoassert": { "version": "1.1.0", @@ -13053,9 +12375,9 @@ "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" }, "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -13079,9 +12401,9 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } @@ -13093,9 +12415,9 @@ "dev": true }, "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/node-addon-api": { "version": "2.0.2", @@ -13140,32 +12462,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, "node_modules/node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", @@ -13176,252 +12472,39 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", - "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", - "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.0", - "set-blocking": "^2.0.0" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": ">=0.10.0" } }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" - }, - "node_modules/node-sass": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.1.tgz", - "integrity": "sha512-uMy+Xt29NlqKCFdFRZyXKOTqGt+QaKHexv9STj2WeLottnlqZEEWx6Bj0MXNthmFRRdM/YwyNo/8Tr46TOM0jQ==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "peer": true, - "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "meow": "^9.0.0", - "nan": "^2.13.2", - "node-gyp": "^8.4.1", - "npmlog": "^5.0.0", - "request": "^2.88.0", - "sass-graph": "4.0.0", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" - }, - "bin": { - "node-sass": "bin/node-sass" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/node-sass/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/node-sass/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/node-sass/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/node-sass/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/node-sass/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-sass/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/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, - "optional": true, - "peer": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" } }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, "engines": { "node": ">=10" }, @@ -13441,24 +12524,10 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/nth-check": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", - "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, "dependencies": { "boolbase": "^1.0.0" @@ -13470,7 +12539,7 @@ "node_modules/number-to-bn": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", + "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", "dependencies": { "bn.js": "4.11.6", "strip-hex-prefix": "1.0.0" @@ -13483,7 +12552,7 @@ "node_modules/number-to-bn/node_modules/bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" }, "node_modules/numeral": { "version": "2.0.6", @@ -13494,9 +12563,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", "dev": true }, "node_modules/oauth-sign": { @@ -13583,15 +12652,15 @@ "node_modules/oboe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", - "integrity": "sha1-VVQoTFQ6ImbXo48X4HOCH73jk80=", + "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", "dependencies": { "http-https": "^1.0.0" } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -13645,19 +12714,11 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "engines": { - "node": ">=4" + "node": ">=12.20" } }, "node_modules/p-limit": { @@ -13708,34 +12769,6 @@ "node": ">=4" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -13792,10 +12825,28 @@ } }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "dependencies": { + "entities": "^4.3.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.1.tgz", + "integrity": "sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/parseurl": { "version": "1.3.3", @@ -13837,17 +12888,18 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/path-parser/-/path-parser-4.2.0.tgz", - "integrity": "sha512-MPPZiWTTp2I72VXmGQQfsn2ohrbd9QTbZSLYNS+HXsnQ37VbiLR/szO2R7DHaZA1V1scYxuxgyQerj+6kMTXtA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/path-parser/-/path-parser-6.1.0.tgz", + "integrity": "sha512-nAB6J73z2rFcQP+870OHhpkHFj5kO4rPLc2Ol4Y3Ale7F6Hk1/cPKp7cQ8RznKF8FOSvu+YR9Xc6Gafk7DlpYA==", "dependencies": { - "search-params": "2.1.3" + "search-params": "3.0.0", + "tslib": "^1.10.0" } }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "node_modules/path-type": { "version": "4.0.0", @@ -13894,6 +12946,14 @@ "resolved": "../../../deps/phoenix_html", "link": true }, + "node_modules/photoswipe": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.4.tgz", + "integrity": "sha512-SN+RWHqxJvdwzXJsh8KrG+ajjPpdTX5HpKglEd0k9o6o5fW+QHPkW8//Bo11MB+NQwTa/hFw8BDv2EdxiDXjNw==", + "engines": { + "node": ">= 0.12.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -13925,9 +12985,9 @@ "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew==" }, "node_modules/pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true, "engines": { "node": ">= 6" @@ -13952,46 +13012,52 @@ } }, "node_modules/postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "dependencies": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-calc": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz", - "integrity": "sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.2" } }, "node_modules/postcss-colormin": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.1.tgz", - "integrity": "sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", + "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", "dev": true, "dependencies": { "browserslist": "^4.16.6", "caniuse-api": "^3.0.0", "colord": "^2.9.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14001,12 +13067,13 @@ } }, "node_modules/postcss-convert-values": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz", - "integrity": "sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", + "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.1.0" + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14016,9 +13083,9 @@ } }, "node_modules/postcss-discard-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", - "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", "dev": true, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14028,9 +13095,9 @@ } }, "node_modules/postcss-discard-duplicates": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", - "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", "dev": true, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14040,9 +13107,9 @@ } }, "node_modules/postcss-discard-empty": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", - "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", "dev": true, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14052,9 +13119,9 @@ } }, "node_modules/postcss-discard-overridden": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", - "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", "dev": true, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14064,17 +13131,17 @@ } }, "node_modules/postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", + "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", "dev": true, "dependencies": { "cosmiconfig": "^7.0.0", "klona": "^2.0.5", - "semver": "^7.3.5" + "semver": "^7.3.8" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", @@ -14086,9 +13153,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -14101,14 +13168,13 @@ } }, "node_modules/postcss-merge-longhand": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.2.tgz", - "integrity": "sha512-BMlg9AXSI5G9TBT0Lo/H3PfUy63P84rVz3BjCFE9e9Y9RXQZD3+h3YO1kgTNsNJy7bBc1YQp8DmSnwLIW5VPcw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", + "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", "dev": true, "dependencies": { - "css-color-names": "^1.0.1", - "postcss-value-parser": "^4.1.0", - "stylehacks": "^5.0.1" + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14118,16 +13184,15 @@ } }, "node_modules/postcss-merge-rules": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.2.tgz", - "integrity": "sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", + "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", "dev": true, "dependencies": { "browserslist": "^4.16.6", "caniuse-api": "^3.0.0", - "cssnano-utils": "^2.0.1", - "postcss-selector-parser": "^6.0.5", - "vendors": "^1.0.3" + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14137,12 +13202,12 @@ } }, "node_modules/postcss-minify-font-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz", - "integrity": "sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14152,14 +13217,14 @@ } }, "node_modules/postcss-minify-gradients": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz", - "integrity": "sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", "dev": true, "dependencies": { "colord": "^2.9.1", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14169,16 +13234,14 @@ } }, "node_modules/postcss-minify-params": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz", - "integrity": "sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", + "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", "dev": true, "dependencies": { - "alphanum-sort": "^1.0.2", - "browserslist": "^4.16.0", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0", - "uniqs": "^2.0.0" + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14188,12 +13251,11 @@ } }, "node_modules/postcss-minify-selectors": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz", - "integrity": "sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", "dev": true, "dependencies": { - "alphanum-sort": "^1.0.2", "postcss-selector-parser": "^6.0.5" }, "engines": { @@ -14263,9 +13325,9 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", - "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", "dev": true, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14275,13 +13337,12 @@ } }, "node_modules/postcss-normalize-display-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz", - "integrity": "sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", "dev": true, "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14291,12 +13352,12 @@ } }, "node_modules/postcss-normalize-positions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz", - "integrity": "sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14306,13 +13367,12 @@ } }, "node_modules/postcss-normalize-repeat-style": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz", - "integrity": "sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", "dev": true, "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14322,12 +13382,12 @@ } }, "node_modules/postcss-normalize-string": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz", - "integrity": "sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14337,13 +13397,12 @@ } }, "node_modules/postcss-normalize-timing-functions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz", - "integrity": "sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", "dev": true, "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14353,13 +13412,13 @@ } }, "node_modules/postcss-normalize-unicode": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz", - "integrity": "sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", + "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", "dev": true, "dependencies": { - "browserslist": "^4.16.0", - "postcss-value-parser": "^4.1.0" + "browserslist": "^4.16.6", + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14369,14 +13428,13 @@ } }, "node_modules/postcss-normalize-url": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.2.tgz", - "integrity": "sha512-k4jLTPUxREQ5bpajFQZpx8bCF2UrlqOTzP9kEqcEnOfwsRshWs2+oAFIHfDQB8GO2PaUaSE0NlTAYtbluZTlHQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", "dev": true, "dependencies": { - "is-absolute-url": "^3.0.3", "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14386,12 +13444,12 @@ } }, "node_modules/postcss-normalize-whitespace": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz", - "integrity": "sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14401,13 +13459,13 @@ } }, "node_modules/postcss-ordered-values": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz", - "integrity": "sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", "dev": true, "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14417,12 +13475,12 @@ } }, "node_modules/postcss-reduce-initial": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz", - "integrity": "sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", + "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", "dev": true, "dependencies": { - "browserslist": "^4.16.0", + "browserslist": "^4.16.6", "caniuse-api": "^3.0.0" }, "engines": { @@ -14433,13 +13491,12 @@ } }, "node_modules/postcss-reduce-transforms": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz", - "integrity": "sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", "dev": true, "dependencies": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14449,9 +13506,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -14462,12 +13519,12 @@ } }, "node_modules/postcss-svgo": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.3.tgz", - "integrity": "sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.1.0", + "postcss-value-parser": "^4.2.0", "svgo": "^2.7.0" }, "engines": { @@ -14478,14 +13535,12 @@ } }, "node_modules/postcss-unique-selectors": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz", - "integrity": "sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", "dev": true, "dependencies": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5", - "uniqs": "^2.0.0" + "postcss-selector-parser": "^6.0.5" }, "engines": { "node": "^10 || ^12 || >=14.0" @@ -14525,26 +13580,18 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "engines": { - "node": ">=4" - } - }, "node_modules/pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.0.0", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -14560,22 +13607,11 @@ } }, "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/printj": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", - "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -14589,29 +13625,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/promise-to-callback": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/promise-to-callback/-/promise-to-callback-1.0.0.tgz", @@ -14761,7 +13774,7 @@ "node_modules/qrcode/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "engines": { "node": ">=4" } @@ -14872,9 +13885,9 @@ } }, "node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "engines": { "node": ">=0.6" } @@ -14901,6 +13914,12 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -14921,17 +13940,6 @@ } ] }, - "node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -14958,12 +13966,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { - "bytes": "3.1.1", - "http-errors": "1.8.1", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -15003,135 +14011,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "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, - "optional": true, - "peer": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/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, - "optional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/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, - "optional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/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, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -15158,41 +14037,26 @@ } }, "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" } }, "node_modules/reduce-reducers": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/reduce-reducers/-/reduce-reducers-0.4.3.tgz", - "integrity": "sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reduce-reducers/-/reduce-reducers-1.0.4.tgz", + "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "node_modules/redux": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", - "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", "dependencies": { "@babel/runtime": "^7.9.2" } @@ -15204,9 +14068,9 @@ "dev": true }, "node_modules/regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "dependencies": { "regenerate": "^1.4.2" @@ -15221,9 +14085,9 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", "dev": true, "dependencies": { "@babel/runtime": "^7.8.4" @@ -15242,15 +14106,15 @@ } }, "node_modules/regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", "dev": true, "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.0.0" }, @@ -15259,15 +14123,15 @@ } }, "node_modules/regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", "dev": true }, "node_modules/regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", "dev": true, "dependencies": { "jsesc": "~0.5.0" @@ -15279,7 +14143,7 @@ "node_modules/regjsparser/node_modules/jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", "dev": true, "bin": { "jsesc": "bin/jsesc" @@ -15350,6 +14214,12 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -15366,6 +14236,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -15406,22 +14281,22 @@ } }, "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "dependencies": { - "lowercase-keys": "^1.0.0" + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, - "optional": true, - "peer": true, + "node_modules/responselike/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "engines": { - "node": ">= 4" + "node": ">=8" } }, "node_modules/reusify": { @@ -15517,9 +14392,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.49.8", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz", - "integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==", + "version": "1.56.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.2.tgz", + "integrity": "sha512-ciEJhnyCRwzlBCB+h5cCPM6ie/6f8HrhZMQOf5vlU60Y1bI1rx5Zb0vlDZvaycHsg/MqFfF1Eq2eokAa32iw8w==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -15533,79 +14408,17 @@ "node": ">=12.0.0" } }, - "node_modules/sass-graph": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.0.tgz", - "integrity": "sha512-WSO/MfXqKH7/TS8RdkCX3lVkPFQzCgbqdGsmSKq6tlPU+GpGEsa/5aW18JqItnqh+lPtcjifqdZ/VmiILkKckQ==", + "node_modules/sass-loader": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", + "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.3.0", - "yargs": "^17.2.1" - }, - "bin": { - "sassgraph": "bin/sassgraph" + "klona": "^2.0.4", + "neo-async": "^2.6.2" }, "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/sass-graph/node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", - "dev": true, - "dependencies": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 12.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", @@ -15613,7 +14426,7 @@ }, "peerDependencies": { "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" @@ -15634,15 +14447,15 @@ } }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/scheduler": { @@ -15655,55 +14468,67 @@ } }, "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" }, "engines": { - "node": ">= 8.9.0" + "node": ">= 12.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" - }, - "node_modules/scss-tokenizer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.3.0.tgz", - "integrity": "sha512-14Zl9GcbBvOT9057ZKjpz5yPOyUWG2ojd9D5io28wHRYsOrs7U95Q+KNL87+32p8rc+LvDpbu/i9ZYjM9Q+FsQ==", + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "js-base64": "^2.4.3", - "source-map": "^0.7.1" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/scss-tokenizer/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">= 8" + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" } }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, "node_modules/search-params": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/search-params/-/search-params-2.1.3.tgz", - "integrity": "sha512-hHxU9ZGWpZ/lrFBIHndSnQae2in7ra+m+tBSoeAahSWDDgOgpZqs4bfaTZpljgNgAgTbjiQoJtZW6FKSsfEcDA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/search-params/-/search-params-3.0.0.tgz", + "integrity": "sha512-8CYNl/bjkEhXWbDTU/K7c2jQtrnqEffIPyOLMqygW/7/b+ym8UtQumcAZjOfMLjZKR6AxK5tOr9fChbQZCzPqg==" }, "node_modules/secp256k1": { "version": "4.0.2", @@ -15741,23 +14566,23 @@ } }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -15774,7 +14599,15 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", @@ -15791,14 +14624,14 @@ } }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -15906,9 +14739,9 @@ } }, "node_modules/signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "node_modules/simple-concat": { @@ -15940,6 +14773,17 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -15955,54 +14799,11 @@ "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", - "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ip": "^1.1.5", - "smart-buffer": "^4.1.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -16026,55 +14827,6 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", @@ -16086,7 +14838,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "node_modules/sshpk": { @@ -16113,24 +14865,11 @@ "node": ">=0.10.0" } }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", "dev": true }, "node_modules/stack-utils": { @@ -16155,50 +14894,11 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/stdout-stream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/stdout-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" + "node": ">= 0.8" } }, "node_modules/stream-browserify": { @@ -16224,7 +14924,7 @@ "node_modules/strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", "engines": { "node": ">=0.10.0" } @@ -16349,20 +15049,6 @@ "npm": ">=3" } }, - "node_modules/strip-indent": { - "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, - "optional": true, - "peer": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -16421,12 +15107,12 @@ } }, "node_modules/stylehacks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", - "integrity": "sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", + "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", "dev": true, "dependencies": { - "browserslist": "^4.16.0", + "browserslist": "^4.16.6", "postcss-selector-parser": "^6.0.4" }, "engines": { @@ -16447,40 +15133,6 @@ "node": ">=4" } }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -16514,15 +15166,15 @@ } }, "node_modules/swarm-js": { - "version": "0.1.40", - "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.40.tgz", - "integrity": "sha512-yqiOCEoA4/IShXkY3WKwP5PvZhmoOOD8clsKA7EEcRILMkTEYHCQ21HDCAcVpmIxZq4LyZvWeRJ6quIyHk1caA==", + "version": "0.1.42", + "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.42.tgz", + "integrity": "sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==", "dependencies": { "bluebird": "^3.5.0", "buffer": "^5.0.5", "eth-lib": "^0.1.26", "fs-extra": "^4.0.2", - "got": "^7.1.0", + "got": "^11.8.5", "mime-types": "^2.1.16", "mkdirp-promise": "^5.0.1", "mock-fs": "^4.1.0", @@ -16531,6 +15183,25 @@ "xhr-request": "^1.0.1" } }, + "node_modules/swarm-js/node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swarm-js/node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, "node_modules/swarm-js/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -16544,44 +15215,48 @@ "minipass": "^2.6.0" } }, - "node_modules/swarm-js/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "node_modules/swarm-js/node_modules/got": { + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, "engines": { - "node": ">=4" + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/swarm-js/node_modules/got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "dependencies": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" + "node_modules/swarm-js/node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">=10.19.0" } }, - "node_modules/swarm-js/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "node_modules/swarm-js/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/swarm-js/node_modules/minipass": { @@ -16602,30 +15277,33 @@ } }, "node_modules/swarm-js/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "node_modules/swarm-js/node_modules/p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/swarm-js/node_modules/prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "node_modules/swarm-js/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/swarm-js/node_modules/safe-buffer": { @@ -16664,28 +15342,18 @@ "node": ">=4.5" } }, - "node_modules/swarm-js/node_modules/url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dependencies": { - "prepend-http": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/swarm-js/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/sweetalert2": { - "version": "11.3.10", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.3.10.tgz", - "integrity": "sha512-/3nhG5QCREkPUndAbOF9h1IM7lgCIU/evsNXd/YUfa9eJ04M+hxksxIio8hhtH16UnWd2GvJ+zvFQ8H9hLRhsw==", + "version": "11.6.15", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.6.15.tgz", + "integrity": "sha512-FqMy1gRGHEI5G145NE5XSP059TziCJu9Xf9/mkki/aKu5pLNcYzjggOzKO5Ex10EBgAGDXQ99jyGfYYzGCYXRQ==", "funding": { - "url": "https://sweetalert2.github.io/#donations" + "type": "individual", + "url": "https://github.com/sponsors/limonte" } }, "node_modules/symbol-tree": { @@ -16703,49 +15371,15 @@ "node": ">=6" } }, - "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", - "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.7.2", "source-map-support": "~0.5.20" }, "bin": { @@ -16808,30 +15442,12 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16852,26 +15468,14 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, "node_modules/timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", "engines": { "node": ">=0.10.0" } }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", - "dev": true - }, "node_modules/tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -16891,14 +15495,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -16914,7 +15510,7 @@ "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, "node_modules/toidentifier": { "version": "1.0.1", @@ -16925,51 +15521,39 @@ } }, "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { "node": ">=6" } }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, - "optional": true, - "peer": true, "engines": { - "node": ">=8" + "node": ">= 4.0.0" } }, - "node_modules/true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "glob": "^7.1.2" + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" } }, "node_modules/tsconfig-paths": { @@ -17142,34 +15726,6 @@ "node": ">=4" } }, - "node_modules/uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -17181,11 +15737,36 @@ "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -17208,29 +15789,20 @@ "querystring": "0.2.0" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, "node_modules/url-set-query": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", - "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=" - }, - "node_modules/url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "engines": { - "node": ">= 4" - } + "integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==" }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", @@ -17255,15 +15827,14 @@ "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, "node_modules/util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", "which-typed-array": "^1.1.2" } }, @@ -17275,7 +15846,7 @@ "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "engines": { "node": ">= 0.4.0" } @@ -17289,47 +15860,20 @@ "uuid": "bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/v8-to-istanbul": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz", - "integrity": "sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^1.6.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/varint": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", @@ -17338,21 +15882,11 @@ "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "engines": { "node": ">= 0.8" } }, - "node_modules/vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -17366,6 +15900,11 @@ "extsprintf": "^1.2.0" } }, + "node_modules/viewerjs": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/viewerjs/-/viewerjs-1.11.1.tgz", + "integrity": "sha512-/VQ2zalHLZJOGIwlxOBtxagLZwNvU3Bf+nm692XlhNFxjBXRxpCVn+GeqmRFg9jK1Y2+Wf8PPGxZgTDN4pHXww==" + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -17376,15 +15915,15 @@ } }, "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", "dev": true, "dependencies": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/walker": { @@ -17397,9 +15936,9 @@ } }, "node_modules/watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -17410,31 +15949,31 @@ } }, "node_modules/web3": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.7.0.tgz", - "integrity": "sha512-n39O7QQNkpsjhiHMJ/6JY6TaLbdX+2FT5iGs8tb3HbIWOhPm4+a7UDbr5Lkm+gLa9aRKWesZs5D5hWyEvg4aJA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.1.tgz", + "integrity": "sha512-tAqFsQhGv340C9OgRJIuoScN7f7wa1tUvsnnDUMt9YE6J4gcm7TV2Uwv+KERnzvV+xgdeuULYpsioRRNKrUvoQ==", "hasInstallScript": true, "dependencies": { - "web3-bzz": "1.7.0", - "web3-core": "1.7.0", - "web3-eth": "1.7.0", - "web3-eth-personal": "1.7.0", - "web3-net": "1.7.0", - "web3-shh": "1.7.0", - "web3-utils": "1.7.0" + "web3-bzz": "1.8.1", + "web3-core": "1.8.1", + "web3-eth": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-shh": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-bzz": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.7.0.tgz", - "integrity": "sha512-XPhTWUnZa8gnARfiqaag3jJ9+6+a66Li8OikgBUJoMUqPuQTCJPncTbGYqOJIfRFGavEAdlMnfYXx9lvgv2ZPw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.1.tgz", + "integrity": "sha512-dJJHS84nvpoxv6ijTMkdUSlRr5beCXNtx4UZcrFLHBva8dT63QEtKdLyDt2AyMJJdVzTCk78uir/6XtVWrdS6w==", "hasInstallScript": true, "dependencies": { "@types/node": "^12.12.6", - "got": "9.6.0", + "got": "12.1.0", "swarm-js": "^0.1.40" }, "engines": { @@ -17442,58 +15981,58 @@ } }, "node_modules/web3-bzz/node_modules/@types/node": { - "version": "12.20.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.42.tgz", - "integrity": "sha512-aI3/oo5DzyiI5R/xAhxxRzfZlWlsbbqdgxfTPkqu/Zt+23GXiJvMCyPJT4+xKSXOnLqoL8jJYMLTwvK2M3a5hw==" + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, "node_modules/web3-core": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.7.0.tgz", - "integrity": "sha512-U/CRL53h3T5KHl8L3njzCBT7fCaHkbE6BGJe3McazvFldRbfTDEHXkUJCyM30ZD0RoLi3aDfTVeFIusmEyCctA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.1.tgz", + "integrity": "sha512-LbRZlJH2N6nS3n3Eo9Y++25IvzMY7WvYnp4NM/Ajhh97dAdglYs6rToQ2DbL2RLvTYmTew4O/y9WmOk4nq9COw==", "dependencies": { - "@types/bn.js": "^4.11.5", + "@types/bn.js": "^5.1.0", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-requestmanager": "1.7.0", - "web3-utils": "1.7.0" + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-requestmanager": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-helpers": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.7.0.tgz", - "integrity": "sha512-kFiqsZFHJliKF8VKZNjt2JvKu3gu7h3N1/ke3EPhdp9Li/rLmiyzFVr6ApryZ1FSjbSx6vyOkibG3m6xQ5EHJA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.1.tgz", + "integrity": "sha512-ClzNO6T1S1gifC+BThw0+GTfcsjLEY8T1qUp6Ly2+w4PntAdNtKahxWKApWJ0l9idqot/fFIDXwO3Euu7I0Xqw==", "dependencies": { - "web3-eth-iban": "1.7.0", - "web3-utils": "1.7.0" + "web3-eth-iban": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-method": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.7.0.tgz", - "integrity": "sha512-43Om+kZX8wU5u1pJ28TltF9e9pSTRph6b8wrOb6wgXAfPHqMulq6UTBJWjXXIRVN46Eiqv0nflw35hp9bbgnbA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.1.tgz", + "integrity": "sha512-oYGRodktfs86NrnFwaWTbv2S38JnpPslFwSSARwFv4W9cjbGUW3LDeA5MKD/dRY+ssZ5OaekeMsUCLoGhX68yA==", "dependencies": { - "@ethersproject/transactions": "^5.0.0-beta.135", - "web3-core-helpers": "1.7.0", - "web3-core-promievent": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-utils": "1.7.0" + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-promievent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.7.0.tgz", - "integrity": "sha512-xPH66XeC0K0k29GoRd0vyPQ07yxERPRd4yVPrbMzGAz/e9E4M3XN//XK6+PdfGvGw3fx8VojS+tNIMiw+PujbQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.1.tgz", + "integrity": "sha512-9mxqHlgB0MrZI4oUIRFkuoJMNj3E7btjrMv3sMer/Z9rYR1PfoSc1aAokw4rxKIcAh+ylVtd/acaB2HKB7aRPg==", "dependencies": { "eventemitter3": "4.0.4" }, @@ -17502,87 +16041,95 @@ } }, "node_modules/web3-core-requestmanager": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.7.0.tgz", - "integrity": "sha512-rA3dBTBPrt+eIfTAQ2/oYNTN/2wbZaYNR3pFZGqG8+2oCK03+7oQyz4sWISKy/nYQhURh4GK01rs9sN4o/Tq9w==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.1.tgz", + "integrity": "sha512-x+VC2YPPwZ1khvqA6TA69LvfFCOZXsoUVOxmTx/vIN22PrY9KzKhxcE7pBSiGhmab1jtmRYXUbcQSVpAXqL8cw==", "dependencies": { "util": "^0.12.0", - "web3-core-helpers": "1.7.0", - "web3-providers-http": "1.7.0", - "web3-providers-ipc": "1.7.0", - "web3-providers-ws": "1.7.0" + "web3-core-helpers": "1.8.1", + "web3-providers-http": "1.8.1", + "web3-providers-ipc": "1.8.1", + "web3-providers-ws": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-subscriptions": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.7.0.tgz", - "integrity": "sha512-6giF8pyJrPmWrRpc2WLoVCvQdMMADp20ZpAusEW72axauZCNlW1XfTjs0i4QHQBfdd2lFp65qad9IuATPhuzrQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.1.tgz", + "integrity": "sha512-bmCMq5OeA3E2vZUh8Js1HcJbhwtsE+yeMqGC4oIZB3XsL5SLqyKLB/pU+qUYqQ9o4GdcrFTDPhPg1bgvf7p1Pw==", "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.0" + "web3-core-helpers": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, + "node_modules/web3-core/node_modules/@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/web3-core/node_modules/@types/node": { - "version": "12.20.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.42.tgz", - "integrity": "sha512-aI3/oo5DzyiI5R/xAhxxRzfZlWlsbbqdgxfTPkqu/Zt+23GXiJvMCyPJT4+xKSXOnLqoL8jJYMLTwvK2M3a5hw==" + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, "node_modules/web3-eth": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.7.0.tgz", - "integrity": "sha512-3uYwjMjn/MZjKIzXCt4YL9ja/k9X5shfa4lKparZhZE6uesmu+xmSmrEFXA/e9qcveF50jkV7frjkT8H+cLYtw==", - "dependencies": { - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-eth-abi": "1.7.0", - "web3-eth-accounts": "1.7.0", - "web3-eth-contract": "1.7.0", - "web3-eth-ens": "1.7.0", - "web3-eth-iban": "1.7.0", - "web3-eth-personal": "1.7.0", - "web3-net": "1.7.0", - "web3-utils": "1.7.0" + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.1.tgz", + "integrity": "sha512-LgyzbhFqiFRd8M8sBXoFN4ztzOnkeckl3H/9lH5ek7AdoRMhBg7tYpYRP3E5qkhd/q+yiZmcUgy1AF6NHrC1wg==", + "dependencies": { + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-accounts": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-eth-ens": "1.8.1", + "web3-eth-iban": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-abi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.7.0.tgz", - "integrity": "sha512-heqR0bWxgCJwjWIhq2sGyNj9bwun5+Xox/LdZKe+WMyTSy0cXDXEAgv3XKNkXC4JqdDt/ZlbTEx4TWak4TRMSg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.1.tgz", + "integrity": "sha512-0mZvCRTIG0UhDhJwNQJgJxu4b4DyIpuMA0GTfqxqeuqzX4Q/ZvmoNurw0ExTfXaGPP82UUmmdkRi6FdZOx+C6w==", "dependencies": { - "@ethersproject/abi": "5.0.7", - "web3-utils": "1.7.0" + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-accounts": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.7.0.tgz", - "integrity": "sha512-Zwm7TlQXdXGRuS6+ib1YsR5fQwpfnFyL6UAZg1zERdrUrs3IkCZSL3yCP/8ZYbAjdTEwWljoott2iSqXNH09ug==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.1.tgz", + "integrity": "sha512-mgzxSYgN54/NsOFBO1Fq1KkXp1S5KlBvI/DlgvajU72rupoFMq6Cu6Yp9GUaZ/w2ij9PzEJuFJk174XwtfMCmg==", "dependencies": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.2", + "@ethereumjs/common": "2.5.0", + "@ethereumjs/tx": "3.3.2", "crypto-browserify": "3.12.0", "eth-lib": "0.2.8", "ethereumjs-util": "^7.0.10", "scrypt-js": "^3.0.1", - "uuid": "3.3.2", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-utils": "1.7.0" + "uuid": "^9.0.0", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" @@ -17604,96 +16151,98 @@ } }, "node_modules/web3-eth-accounts/node_modules/uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/web3-eth-contract": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.7.0.tgz", - "integrity": "sha512-2LY1Xwxu5rx468nqHuhvupQAIpytxIUj3mGL9uexszkhrQf05THVe3i4OnUCzkeN6B2cDztNOqLT3j9SSnVQDg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.1.tgz", + "integrity": "sha512-1wphnl+/xwCE2io44JKnN+ti3oa47BKRiVzvWd42icwRbcpFfRxH9QH+aQX3u8VZIISNH7dAkTWpGIIJgGFTmg==", "dependencies": { - "@types/bn.js": "^4.11.5", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-promievent": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-eth-abi": "1.7.0", - "web3-utils": "1.7.0" + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, + "node_modules/web3-eth-contract/node_modules/@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/web3-eth-ens": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.7.0.tgz", - "integrity": "sha512-I1bikYJJWQ/FJZIAvwsGOvzAgcRIkosWG4s1L6veRoXaU8OEJFeh4s00KcfHDxg7GWZZGbUSbdbzKpwRbWnvkg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.1.tgz", + "integrity": "sha512-FT8xTI9uN8RxeBQa/W8pLa2aoFh4+EE34w7W2271LICKzla1dtLyb6XSdn48vsUcPmhWsTVk9mO9RTU0l4LGQQ==", "dependencies": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-promievent": "1.7.0", - "web3-eth-abi": "1.7.0", - "web3-eth-contract": "1.7.0", - "web3-utils": "1.7.0" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-iban": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.7.0.tgz", - "integrity": "sha512-1PFE/Og+sPZaug+M9TqVUtjOtq0HecE+SjDcsOOysXSzslNC2CItBGkcRwbvUcS+LbIkA7MFsuqYxOL0IV/gyA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.1.tgz", + "integrity": "sha512-DomoQBfvIdtM08RyMGkMVBOH0vpOIxSSQ+jukWk/EkMLGMWJtXw/K2c2uHAeq3L/VPWNB7zXV2DUEGV/lNE2Dg==", "dependencies": { - "bn.js": "^4.11.9", - "web3-utils": "1.7.0" + "bn.js": "^5.2.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, - "node_modules/web3-eth-iban/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/web3-eth-personal": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.7.0.tgz", - "integrity": "sha512-Dr9RZTNOR80PhrPKGdktDUXpOgExEcCcosBj080lKCJFU1paSPj9Zfnth3u6BtIOXyKsVFTrpqekqUDyAwXnNw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.1.tgz", + "integrity": "sha512-myIYMvj7SDIoV9vE5BkVdon3pya1WinaXItugoii2VoTcQNPOtBxmYVH+XS5ErzCJlnxzphpQrkywyY64bbbCA==", "dependencies": { "@types/node": "^12.12.6", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-net": "1.7.0", - "web3-utils": "1.7.0" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-personal/node_modules/@types/node": { - "version": "12.20.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.42.tgz", - "integrity": "sha512-aI3/oo5DzyiI5R/xAhxxRzfZlWlsbbqdgxfTPkqu/Zt+23GXiJvMCyPJT4+xKSXOnLqoL8jJYMLTwvK2M3a5hw==" + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, "node_modules/web3-net": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.7.0.tgz", - "integrity": "sha512-8pmfU1Se7DmG40Pu8nOCKlhuI12VsVzCtdFDnLAai0zGVAOUuuOCK71B2aKm6u9amWBJjtOlyrCwvsG+QEd6dw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.1.tgz", + "integrity": "sha512-LyEJAwogdFo0UAXZqoSJGFjopdt+kLw0P00FSZn2yszbgcoI7EwC+nXiOsEe12xz4LqpYLOtbR7+gxgiTVjjHQ==", "dependencies": { - "web3-core": "1.7.0", - "web3-core-method": "1.7.0", - "web3-utils": "1.7.0" + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" }, "engines": { "node": ">=8.0.0" @@ -17778,36 +16327,46 @@ } }, "node_modules/web3-providers-http": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.7.0.tgz", - "integrity": "sha512-Y9reeEiApfvQKLUUtrU4Z0c+H6b7BMWcsxjgoXndI1C5NB297mIUfltXxfXsh5C/jk5qn4Q3sJp3SwQTyVjH7Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.1.tgz", + "integrity": "sha512-1Zyts4O9W/UNEPkp+jyL19Jc3D15S4yp8xuLTjVhcUEAlHo24NDWEKxtZGUuHk4HrKL2gp8OlsDbJ7MM+ESDgg==", "dependencies": { - "web3-core-helpers": "1.7.0", - "xhr2-cookies": "1.1.0" + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, + "node_modules/web3-providers-http/node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/web3-providers-ipc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.7.0.tgz", - "integrity": "sha512-U5YLXgu6fvAK4nnMYqo9eoml3WywgTym0dgCdVX/n1UegLIQ4nctTubBAuWQEJzmAzwh+a6ValGcE7ZApTRI7Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.1.tgz", + "integrity": "sha512-nw/W5nclvi+P2z2dYkLWReKLnocStflWqFl+qjtv0xn3MrUTyXMzSF0+61i77+16xFsTgzo4wS/NWIOVkR0EFA==", "dependencies": { "oboe": "2.1.5", - "web3-core-helpers": "1.7.0" + "web3-core-helpers": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-ws": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.7.0.tgz", - "integrity": "sha512-0a8+lVV3JBf+eYnGOsdzOpftK1kis5X7s35QAdoaG5SDapnEylXFlR4xDSSSU88ZwMwvse8hvng2xW6A7oeWxw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.1.tgz", + "integrity": "sha512-TNefIDAMpdx57+YdWpYZ/xdofS0P+FfKaDYXhn24ie/tH9G+AB+UBSOKnjN0KSadcRSCMBwGPRiEmNHPavZdsA==", "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.0", + "web3-core-helpers": "1.8.1", "websocket": "^1.0.32" }, "engines": { @@ -17815,26 +16374,26 @@ } }, "node_modules/web3-shh": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.7.0.tgz", - "integrity": "sha512-RZhxcevALIPK178VZCpwMBvQeW+IoWtRJ4EMdegpbnETeZaC3aRUcs6vKnrf0jXJjm4J/E2Dt438Y1Ord/1IMw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.1.tgz", + "integrity": "sha512-sqHgarnfcY2Qt3PYS4R6YveHrDy7hmL09yeLLHHCI+RKirmjLVqV0rc5LJWUtlbYI+kDoa5gbgde489M9ZAC0g==", "hasInstallScript": true, "dependencies": { - "web3-core": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-net": "1.7.0" + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-net": "1.8.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-utils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.0.tgz", - "integrity": "sha512-O8Tl4Ky40Sp6pe89Olk2FsaUkgHyb5QAXuaKo38ms3CxZZ4d3rPGfjP9DNKGm5+IUgAZBNpF1VmlSmNCqfDI1w==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.1.tgz", + "integrity": "sha512-LgnM9p6V7rHHUGfpMZod+NST8cRfGzJ1BTXAyNo7A9cJX9LczBfSRxJp+U/GInYe9mby40t3v22AJdlELibnsQ==", "dependencies": { - "bn.js": "^4.11.9", + "bn.js": "^5.2.1", "ethereum-bloom-filters": "^1.0.6", "ethereumjs-util": "^7.1.0", "ethjs-unit": "0.1.6", @@ -17846,15 +16405,10 @@ "node": ">=8.0.0" } }, - "node_modules/web3-utils/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/web3modal": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/web3modal/-/web3modal-1.9.5.tgz", - "integrity": "sha512-L5ME6zgoaCDa+T66skW9WpxGOJX6vU9v+7aLacoQJhU3AMTk784ionpX+Pg4UdhdM+UQW+odge32GkwEX11czQ==", + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/web3modal/-/web3modal-1.9.10.tgz", + "integrity": "sha512-gRByp+toRiADwkJLLGRXsnIVbLS1aJB71sJyryS6C7cF6jJ3cRN1LbPYEMObMyJkyjOZonx0CNZVAYGiD099aA==", "dependencies": { "detect-browser": "^5.1.0", "prop-types": "^15.7.2", @@ -17865,18 +16419,18 @@ } }, "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "engines": { - "node": ">=10.4" + "node": ">=12" } }, "node_modules/webpack": { - "version": "5.69.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz", - "integrity": "sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A==", + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -17884,24 +16438,24 @@ "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "bin": { @@ -17921,40 +16475,42 @@ } }, "node_modules/webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -17963,6 +16519,15 @@ } } }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/webpack-merge": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", @@ -17976,18 +16541,6 @@ "node": ">=10.0.0" } }, - "node_modules/webpack/node_modules/acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/webpack/node_modules/acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", @@ -18051,15 +16604,30 @@ "node_modules/websocket/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "dependencies": { - "iconv-lite": "0.4.24" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/whatwg-fetch": { @@ -18068,23 +16636,25 @@ "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/which": { @@ -18120,7 +16690,7 @@ "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" }, "node_modules/which-typed-array": { "version": "1.1.7", @@ -18141,17 +16711,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -18223,24 +16782,25 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", "dev": true, "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", @@ -18291,16 +16851,19 @@ "node_modules/xhr2-cookies": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", - "integrity": "sha1-fXdEnQmZGX8VXLc7I99yUF7YnUg=", + "integrity": "sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==", "dependencies": { "cookiejar": "^2.1.1" } }, "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/xmlchars": { "version": "2.2.0", @@ -18309,9 +16872,9 @@ "dev": true }, "node_modules/xss": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.10.tgz", - "integrity": "sha512-qmoqrRksmzqSKvgqzN0055UFWY7OKx1/9JWeRswwEVX9fCG5jcYRxa/A2DHcmZX6VJvjzHRQ2STeeVcQkrmLSw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", + "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" @@ -18344,7 +16907,7 @@ "node_modules/yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", "engines": { "node": ">=0.10.32" } @@ -18365,30 +16928,21 @@ } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/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, - "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs/node_modules/y18n": { @@ -18400,6 +16954,15 @@ "node": ">=10" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -18414,114 +16977,132 @@ } }, "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "requires": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.18.6" } }, "@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==" + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", + "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==" }, "@babel/core": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", - "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.12", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.10", - "@babel/types": "^7.16.8", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.1", + "semver": "^6.3.0" } }, "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" } }, "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" } }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", "semver": "^6.3.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.10.tgz", - "integrity": "sha512-wDeej0pu3WN/ffTxMNCPW5UCiOav8IcLRxSIyp/9+IF2xJUM9h/OYjg0IJLHaL6F8oU8kqMz9nc1vryXhMsgXg==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.7.tgz", - "integrity": "sha512-fk5A6ymfp+O5+p2yCkXAu5Kyj6v0xh0RBeNcAkYUMDvvAAoxvSKXn+Jb37t/yWFiQVDFK1ELpUTD8/aLhCPu+g==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" } }, "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2", @@ -18529,366 +17110,361 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "requires": { - "@babel/types": "^7.16.7" - } + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" }, "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", "requires": { - "@babel/types": "^7.16.7" + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" } }, "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.9" } }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" } }, "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" } }, "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" } }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.20.2" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.18.9" } }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" }, "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" } }, "@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" } }, "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", - "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", - "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", - "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", "@babel/plugin-syntax-async-generators": "^7.8.4" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz", - "integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", - "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.7.tgz", - "integrity": "sha512-3O0Y4+dw94HA86qSg9IHfyPktgR7q3gpNVAeiKQd+8jBKFaU5NQS1Yatgo4wY+UFNuLjvxcSmzcsHqrhgTyBUA==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.16.7" + "@babel/plugin-transform-parameters": "^7.20.1" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", - "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-syntax-async-generators": { @@ -18945,6 +17521,15 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, "@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -18963,6 +17548,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -19036,261 +17630,261 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.19.0" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz", + "integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@babel/plugin-transform-destructuring": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz", - "integrity": "sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", - "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" } }, "@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", - "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", - "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", - "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" } }, "@babel/plugin-transform-new-target": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", - "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" } }, "@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz", + "integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", "dev": true, "requires": { - "regenerator-transform": "^0.14.2" + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", - "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-runtime": { @@ -19318,102 +17912,103 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.11", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -19423,45 +18018,56 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.8", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", "semver": "^6.3.0" + }, + "dependencies": { + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + } } }, "@babel/preset-modules": { @@ -19486,38 +18092,39 @@ } }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" } }, "@babel/traverse": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", - "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.10", - "@babel/types": "^7.16.8", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" } }, @@ -19557,14 +18164,14 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.2", + "espree": "^9.4.0", "globals": "^13.15.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -19580,9 +18187,9 @@ "dev": true }, "globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -19615,247 +18222,226 @@ } }, "@ethereumjs/common": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.0.tgz", - "integrity": "sha512-Cq2qS0FTu6O2VU1sgg+WyU9Ps0M6j/BEMHN+hRaECXCV/r0aI78u4N6p52QW/BDVhwWZpCdrvG8X7NJdzlpNUA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz", + "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==", "requires": { "crc-32": "^1.2.0", - "ethereumjs-util": "^7.1.3" + "ethereumjs-util": "^7.1.1" } }, "@ethereumjs/tx": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.4.0.tgz", - "integrity": "sha512-WWUwg1PdjHKZZxPPo274ZuPsJCWV3SqATrEKQP1n2DrVYVP1aZIYpo/mFaA0BDoE0tIQmBeimRCEA0Lgil+yYw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz", + "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==", "requires": { - "@ethereumjs/common": "^2.6.0", - "ethereumjs-util": "^7.1.3" + "@ethereumjs/common": "^2.5.0", + "ethereumjs-util": "^7.1.2" } }, "@ethersproject/abi": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.7.tgz", - "integrity": "sha512-Cqktk+hSIckwP/W8O47Eef60VwmoSC/L3lY0+dIBhQPCNn9E4V7rwmm2aFrNRRDJfFlGuZ1khkQUOc3oBX+niw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", "requires": { - "@ethersproject/address": "^5.0.4", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/hash": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/strings": "^5.0.4" + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" } }, "@ethersproject/abstract-provider": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz", - "integrity": "sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", "requires": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/networks": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/transactions": "^5.5.0", - "@ethersproject/web": "^5.5.0" + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" } }, "@ethersproject/abstract-signer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz", - "integrity": "sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", "requires": { - "@ethersproject/abstract-provider": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0" + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" } }, "@ethersproject/address": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.5.0.tgz", - "integrity": "sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", "requires": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/rlp": "^5.5.0" + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" } }, "@ethersproject/base64": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.5.0.tgz", - "integrity": "sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", "requires": { - "@ethersproject/bytes": "^5.5.0" + "@ethersproject/bytes": "^5.7.0" } }, "@ethersproject/bignumber": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.5.0.tgz", - "integrity": "sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "bn.js": "^4.11.9" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" } }, "@ethersproject/bytes": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.5.0.tgz", - "integrity": "sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", "requires": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.7.0" } }, "@ethersproject/constants": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.5.0.tgz", - "integrity": "sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", "requires": { - "@ethersproject/bignumber": "^5.5.0" + "@ethersproject/bignumber": "^5.7.0" } }, "@ethersproject/hash": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.5.0.tgz", - "integrity": "sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", "requires": { - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" } }, "@ethersproject/keccak256": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.5.0.tgz", - "integrity": "sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", "requires": { - "@ethersproject/bytes": "^5.5.0", + "@ethersproject/bytes": "^5.7.0", "js-sha3": "0.8.0" } }, "@ethersproject/logger": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.5.0.tgz", - "integrity": "sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==" + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==" }, "@ethersproject/networks": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.5.2.tgz", - "integrity": "sha512-NEqPxbGBfy6O3x4ZTISb90SjEDkWYDUbEeIFhJly0F7sZjoQMnj5KYzMSkMkLKZ+1fGpx00EDpHQCy6PrDupkQ==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", "requires": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.7.0" } }, "@ethersproject/properties": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.5.0.tgz", - "integrity": "sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", "requires": { - "@ethersproject/logger": "^5.5.0" + "@ethersproject/logger": "^5.7.0" } }, "@ethersproject/rlp": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.5.0.tgz", - "integrity": "sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" } }, "@ethersproject/signing-key": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.5.0.tgz", - "integrity": "sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==", - "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "bn.js": "^4.11.9", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", "elliptic": "6.5.4", "hash.js": "1.1.7" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } } }, "@ethersproject/strings": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.5.0.tgz", - "integrity": "sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", "requires": { - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/logger": "^5.5.0" + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" } }, "@ethersproject/transactions": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.5.0.tgz", - "integrity": "sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", "requires": { - "@ethersproject/address": "^5.5.0", - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/constants": "^5.5.0", - "@ethersproject/keccak256": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/rlp": "^5.5.0", - "@ethersproject/signing-key": "^5.5.0" + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" } }, "@ethersproject/web": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.5.1.tgz", - "integrity": "sha512-olvLvc1CB12sREc1ROPSHTdFCdvMh0J5GSJYiQg2D0hdD4QmJDy8QYDb1CvoqD/bF1c++aeKv2sR5uduuG9dQg==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", "requires": { - "@ethersproject/base64": "^5.5.0", - "@ethersproject/bytes": "^5.5.0", - "@ethersproject/logger": "^5.5.0", - "@ethersproject/properties": "^5.5.0", - "@ethersproject/strings": "^5.5.0" + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" } }, "@fortawesome/fontawesome-free": { - "version": "6.0.0-beta3", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.0.0-beta3.tgz", - "integrity": "sha512-4SqOuhC8tSLeQvbW1nDmq6T7+8vdSgHy/w7PRwCFzMQCbKuYFIir/3UuWsV1QblX1lt7SGlSgwbaCv9XhRt8HA==" - }, - "@gar/promisify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz", - "integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==", - "dev": true, - "optional": true, - "peer": true + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz", + "integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==" }, "@humanwhocodes/config-array": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", - "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", + "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -19863,6 +18449,12 @@ "minimatch": "^3.0.4" } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -19940,16 +18532,16 @@ "dev": true }, "@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", + "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", "slash": "^3.0.0" }, "dependencies": { @@ -20005,37 +18597,37 @@ } }, "@jest/core": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", + "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", "dev": true, "requires": { - "@jest/console": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/reporters": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.8.1", + "ci-info": "^3.2.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.2.0", + "jest-config": "^29.3.1", + "jest-haste-map": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-regex-util": "^29.2.0", + "jest-resolve": "^29.3.1", + "jest-resolve-dependencies": "^29.3.1", + "jest-runner": "^29.3.1", + "jest-runtime": "^29.3.1", + "jest-snapshot": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", + "jest-watcher": "^29.3.1", "micromatch": "^4.0.4", - "rimraf": "^3.0.0", + "pretty-format": "^29.3.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -20092,73 +18684,92 @@ } }, "@jest/environment": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", + "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", "dev": true, "requires": { - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", - "jest-mock": "^27.4.6" + "jest-mock": "^29.3.1" + } + }, + "@jest/expect": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", + "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", + "dev": true, + "requires": { + "expect": "^29.3.1", + "jest-snapshot": "^29.3.1" + } + }, + "@jest/expect-utils": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", + "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", + "dev": true, + "requires": { + "jest-get-type": "^29.2.0" } }, "@jest/fake-timers": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", + "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "@sinonjs/fake-timers": "^8.0.1", + "@jest/types": "^29.3.1", + "@sinonjs/fake-timers": "^9.1.2", "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" } }, "@jest/globals": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", + "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" + "@jest/environment": "^29.3.1", + "@jest/expect": "^29.3.1", + "@jest/types": "^29.3.1", + "jest-mock": "^29.3.1" } }, "@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", + "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", + "@jridgewell/trace-mapping": "^0.3.15", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", + "jest-worker": "^29.3.1", "slash": "^3.0.0", - "source-map": "^0.6.0", "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "dependencies": { "ansi-styles": { @@ -20201,11 +18812,28 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "jest-worker": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", + "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.3.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, "supports-color": { "version": "7.2.0", @@ -20218,70 +18846,71 @@ } } }, + "@jest/schemas": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", + "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, "@jest/source-map": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", + "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.15", "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "graceful-fs": "^4.2.9" } }, "@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", + "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", "dev": true, "requires": { - "@jest/console": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/types": "^29.3.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", + "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", "dev": true, "requires": { - "@jest/test-result": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" + "@jest/test-result": "^29.3.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "slash": "^3.0.0" } }, "@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", + "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", "dev": true, "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", + "@babel/core": "^7.11.6", + "@jest/types": "^29.3.1", + "@jridgewell/trace-mapping": "^0.3.15", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "jest-regex-util": "^29.2.0", + "jest-util": "^29.3.1", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "write-file-atomic": "^4.0.1" }, "dependencies": { "ansi-styles": { @@ -20318,18 +18947,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -20342,15 +18971,16 @@ } }, "@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", + "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", "dev": true, "requires": { + "@jest/schemas": "^29.0.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "dependencies": { @@ -20405,6 +19035,50 @@ } } }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@metamask/safe-event-emitter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz", @@ -20436,47 +19110,16 @@ "fastq": "^1.6.0" } }, - "@npmcli/fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.0.0.tgz", - "integrity": "sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } + "@sinclair/typebox": { + "version": "0.24.27", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.27.tgz", + "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==", + "dev": true }, "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" }, "@sinonjs/commons": { "version": "1.8.3", @@ -20488,32 +19131,26 @@ } }, "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" } }, "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "requires": { - "defer-to-connect": "^1.0.1" + "defer-to-connect": "^2.0.1" } }, "@tarekraafat/autocomplete.js": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@tarekraafat/autocomplete.js/-/autocomplete.js-10.2.6.tgz", - "integrity": "sha512-M3awL75YTQVHev4XlWGRKJalwVG3MGgtNnbRdtNipcyMcAV1sfkireBM8BKL/vhFhrus+6/5KZfj/DJDu8X0mg==" - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/@tarekraafat/autocomplete.js/-/autocomplete.js-10.2.7.tgz", + "integrity": "sha512-iE+dnXI8/LrTaSORrnNdSyXg/bFCbCpz/R5GUdB3ioW+9PVEhglxNcSDQNeCXtrbRG0kOBFUd4unEiwcmqyn8A==" }, "@trysound/sax": { "version": "0.2.0", @@ -20522,9 +19159,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.18", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", - "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", + "version": "7.1.20", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", + "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -20554,9 +19191,9 @@ } }, "@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", + "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -20570,6 +19207,17 @@ "@types/node": "*" } }, + "@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -20605,6 +19253,11 @@ "@types/node": "*" } }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -20629,6 +19282,17 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -20641,27 +19305,19 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "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, - "optional": true, - "peer": true + "@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "requires": { + "@types/node": "*" + } }, "@types/node": { "version": "16.11.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==" }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true, - "optional": true, - "peer": true - }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -20677,11 +19333,19 @@ } }, "@types/prettier": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz", - "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", "dev": true }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", @@ -20696,28 +19360,34 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", + "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", "dev": true, "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, "@walletconnect/browser-utils": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.7.8.tgz", - "integrity": "sha512-iCL0XCWOZaABIc0lqA79Vyaybr3z26nt8mxiwvfrG8oaKUf5Y21Of4dj+wIXQ4Hhblre6SgDlU0Ffb39+1THOw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.8.0.tgz", + "integrity": "sha512-Wcqqx+wjxIo9fv6eBUFHPsW1y/bGWWRboni5dfD8PtOmrihrEpOCmvRJe4rfl7xgJW8Ea9UqKEaq0bIRLHlK4A==", "requires": { "@walletconnect/safe-json": "1.0.0", - "@walletconnect/types": "^1.7.8", + "@walletconnect/types": "^1.8.0", "@walletconnect/window-getters": "1.0.0", "@walletconnect/window-metadata": "1.0.0", "detect-browser": "5.2.0" @@ -20731,24 +19401,24 @@ } }, "@walletconnect/client": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.7.8.tgz", - "integrity": "sha512-pBroM6jZAaUM0SoXJZg5U7aPTiU3ljQAw3Xh/i2pxFDeN/oPKao7husZ5rdxS5xuGSV6YpqqRb0RxW1IeoR2Pg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.8.0.tgz", + "integrity": "sha512-svyBQ14NHx6Cs2j4TpkQaBI/2AF4+LXz64FojTjMtV4VMMhl81jSO1vNeg+yYhQzvjcGH/GpSwixjyCW0xFBOQ==", "requires": { - "@walletconnect/core": "^1.7.8", - "@walletconnect/iso-crypto": "^1.7.8", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8" + "@walletconnect/core": "^1.8.0", + "@walletconnect/iso-crypto": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" } }, "@walletconnect/core": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.7.8.tgz", - "integrity": "sha512-9xcQ0YNf9JUFb0YOX1Mpy4Yojt+6w2yQz/0aIEyj2X/s9D71NW0fTYsMcdhkLOI7mn2cqVbx2t1lRvdgqsbrSQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.8.0.tgz", + "integrity": "sha512-aFTHvEEbXcZ8XdWBw6rpQDte41Rxwnuk3SgTD8/iKGSRTni50gI9S3YEzMj05jozSiOBxQci4pJDMVhIUMtarw==", "requires": { - "@walletconnect/socket-transport": "^1.7.8", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8" + "@walletconnect/socket-transport": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" } }, "@walletconnect/crypto": { @@ -20778,12 +19448,12 @@ "integrity": "sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ==" }, "@walletconnect/http-connection": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/http-connection/-/http-connection-1.7.8.tgz", - "integrity": "sha512-31gjBw46MRU9hFMTNXAqh+f8qpDNzVeV9BJehzVWKiNC3ciL1JCZkbvsY0djwajduE6TB2ujaML0XDXS9HgBRA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/http-connection/-/http-connection-1.8.0.tgz", + "integrity": "sha512-IziEr3c53qsMromK7jz0EkbKDHlryRbxXdFR+xaG+S5nfxtUdAfjzlZabvczXdDCgmTij6KbNsZAjBMqCBzACw==", "requires": { - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0", "eventemitter3": "4.0.7", "xhr2-cookies": "1.1.0" }, @@ -20796,30 +19466,30 @@ } }, "@walletconnect/iso-crypto": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.7.8.tgz", - "integrity": "sha512-Qo6qDcMG0Ac+9fpWE0h/oE55NHLk6eM2vlXpWlQDN/me7RZGrkvk+LXsAkQ3UiYPEiPfq4eswcyRWC9AcrAscg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.8.0.tgz", + "integrity": "sha512-pWy19KCyitpfXb70hA73r9FcvklS+FvO9QUIttp3c2mfW8frxgYeRXfxLRCIQTkaYueRKvdqPjbyhPLam508XQ==", "requires": { "@walletconnect/crypto": "^1.0.2", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8" + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" } }, "@walletconnect/jsonrpc-types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.0.tgz", - "integrity": "sha512-11QXNq5H1PKZk7bP8SxgmCw3HRaDuPOVE+wObqEvmhc7OWYUZqfuaaMb+OXGRSOHL3sbC+XHfdeCxFTMXSFyng==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.1.tgz", + "integrity": "sha512-+6coTtOuChCqM+AoYyi4Q83p9l/laI6NvuM2/AHaZFuf0gT0NjW7IX2+86qGyizn7Ptq4AYZmfxurAxTnhefuw==", "requires": { "keyvaluestorage-interface": "^1.0.0" } }, "@walletconnect/jsonrpc-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.0.tgz", - "integrity": "sha512-qUHbKUK6sHeHn67qtHZoLoYk5hS6x1arTPjKDRkY93/6Fx+ZmNIpdm1owX3l6aYueyegJ7mz43FpvYHUqJ8xcw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.3.tgz", + "integrity": "sha512-3yb49bPk16MNLk6uIIHPSHQCpD6UAo1OMOx1rM8cW/MPEAYAzrSW5hkhG7NEUwX9SokRIgnZK3QuQkiyNzBMhQ==", "requires": { "@walletconnect/environment": "^1.0.0", - "@walletconnect/jsonrpc-types": "^1.0.0" + "@walletconnect/jsonrpc-types": "^1.0.1" } }, "@walletconnect/mobile-registry": { @@ -20828,13 +19498,13 @@ "integrity": "sha512-ZtKRio4uCZ1JUF7LIdecmZt7FOLnX72RPSY7aUVu7mj7CSfxDwUn6gBuK6WGtH+NZCldBqDl5DenI5fFSvkKYw==" }, "@walletconnect/qrcode-modal": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.7.8.tgz", - "integrity": "sha512-LqNJMLWO+ljvoRSdq8tcEslW0imKrrb+ugs3bw4w/jEI1FSJzVeinEsgVpyaMv8wsUcyTcSCXSkXpT1SXHtcpw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.8.0.tgz", + "integrity": "sha512-BueaFefaAi8mawE45eUtztg3ZFbsAH4DDXh1UNwdUlsvFMjqcYzLUG0xZvDd6z2eOpbgDg2N3bl6gF0KONj1dg==", "requires": { - "@walletconnect/browser-utils": "^1.7.8", + "@walletconnect/browser-utils": "^1.8.0", "@walletconnect/mobile-registry": "^1.4.0", - "@walletconnect/types": "^1.7.8", + "@walletconnect/types": "^1.8.0", "copy-to-clipboard": "^3.3.1", "preact": "10.4.1", "qrcode": "1.4.4" @@ -20856,12 +19526,12 @@ "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg==" }, "@walletconnect/socket-transport": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.7.8.tgz", - "integrity": "sha512-bqEjLxfSzG79v2OT7XVOZoyUkg6g3yng0fURrdLusWs42fYHWnrSrIZDejFn8N5PiZk5R2edrggkQ7w0VUUAfw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.8.0.tgz", + "integrity": "sha512-5DyIyWrzHXTcVp0Vd93zJ5XMW61iDM6bcWT4p8DTRfFsOtW46JquruMhxOLeCOieM4D73kcr3U7WtyR4JUsGuQ==", "requires": { - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0", "ws": "7.5.3" }, "dependencies": { @@ -20874,19 +19544,19 @@ } }, "@walletconnect/types": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.7.8.tgz", - "integrity": "sha512-0oSZhKIrtXRJVP1jQ0EDTRtotQY6kggGjDcmm/LLQBKnOZXdPeo0sPkV/7DjT5plT3O7Cjc6JvuXt9WOY0hlCA==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.8.0.tgz", + "integrity": "sha512-Cn+3I0V0vT9ghMuzh1KzZvCkiAxTq+1TR2eSqw5E5AVWfmCtECFkVZBP6uUJZ8YjwLqXheI+rnjqPy7sVM4Fyg==" }, "@walletconnect/utils": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.7.8.tgz", - "integrity": "sha512-DSpfH6Do0TQmdrgzu+SyjVhupVjN0WEMvNWGK9K4VlSmLFpCWfme7qxzrvuxBZ47gDqs1kGWvjyJmviWqvOnAg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.8.0.tgz", + "integrity": "sha512-zExzp8Mj1YiAIBfKNm5u622oNw44WOESzo6hj+Q3apSMIb0Jph9X3GDIdbZmvVZsNPxWDL7uodKgZcCInZv2vA==", "requires": { - "@walletconnect/browser-utils": "^1.7.8", + "@walletconnect/browser-utils": "^1.8.0", "@walletconnect/encoding": "^1.0.1", - "@walletconnect/jsonrpc-utils": "^1.0.0", - "@walletconnect/types": "^1.7.8", + "@walletconnect/jsonrpc-utils": "^1.0.3", + "@walletconnect/types": "^1.8.0", "bn.js": "4.11.8", "js-sha3": "0.8.0", "query-string": "6.13.5" @@ -20910,20 +19580,20 @@ "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" } } }, "@walletconnect/web3-provider": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@walletconnect/web3-provider/-/web3-provider-1.7.8.tgz", - "integrity": "sha512-2VxGo7KPfQTWRJ+rygt3ok/u04InkVE+H9LBIF/RMUwcwyGf2nsP3CcYZVcg3yYpacgN7bAZCersCEYwU8AeeA==", - "requires": { - "@walletconnect/client": "^1.7.8", - "@walletconnect/http-connection": "^1.7.8", - "@walletconnect/qrcode-modal": "^1.7.8", - "@walletconnect/types": "^1.7.8", - "@walletconnect/utils": "^1.7.8", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/web3-provider/-/web3-provider-1.8.0.tgz", + "integrity": "sha512-lqqEO0oRmCehH+c8ZPk3iH7I7YtbzmkWd58/Or2AgWAl869JamzndKCD3sTlNsPRQLxxPpraHQqzur7uclLWvg==", + "requires": { + "@walletconnect/client": "^1.8.0", + "@walletconnect/http-connection": "^1.8.0", + "@walletconnect/qrcode-modal": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0", "web3-provider-engine": "16.0.1" } }, @@ -21087,25 +19757,23 @@ } }, "@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "requires": {} }, "@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true, "requires": {} }, @@ -21122,18 +19790,15 @@ "dev": true }, "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "optional": true, - "peer": true + "abortcontroller-polyfill": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", + "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" }, "abstract-leveldown": { "version": "2.6.3", @@ -21144,18 +19809,18 @@ } }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true }, "acorn-globals": { @@ -21166,6 +19831,14 @@ "requires": { "acorn": "^7.1.1", "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } } }, "acorn-jsx": { @@ -21195,31 +19868,6 @@ "debug": "4" } }, - "agentkeepalive": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.0.tgz", - "integrity": "sha512-0PhAp58jZNw13UJv7NVdTGb0ZcghHUb3DrZ046JiiJY/BOaTTpbwdHq2VObPCBV8M2GPh7sgrJ3AQ8Ey468LJw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -21267,12 +19915,6 @@ "dev": true, "requires": {} }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -21306,26 +19948,6 @@ "picomatch": "^2.0.4" } }, - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true, - "optional": true, - "peer": true - }, - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -21338,7 +19960,7 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "array-includes": { "version": "3.1.4", @@ -21353,12 +19975,6 @@ "is-string": "^1.0.7" } }, - "array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", - "dev": true - }, "array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -21370,14 +19986,6 @@ "es-abstract": "^1.19.0" } }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true, - "optional": true, - "peer": true - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -21441,14 +20049,6 @@ "async": "^2.4.0" } }, - "async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", - "dev": true, - "optional": true, - "peer": true - }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -21475,14 +20075,14 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "autoprefixer": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", - "integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==", + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", "dev": true, "requires": { - "browserslist": "^4.19.1", - "caniuse-lite": "^1.0.30001297", - "fraction.js": "^4.1.2", + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -21504,18 +20104,17 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", + "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", "dev": true, "requires": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/transform": "^29.3.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", + "babel-preset-jest": "^29.2.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "dependencies": { @@ -21571,24 +20170,13 @@ } }, "babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", - "dev": true, - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", + "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", "dev": true, "requires": { - "object.assign": "^4.1.0" + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" } }, "babel-plugin-istanbul": { @@ -21605,35 +20193,35 @@ } }, "babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", + "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", "dev": true, "requires": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", "semver": "^6.1.1" } }, "babel-plugin-polyfill-corejs3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.1.tgz", - "integrity": "sha512-TihqEe4sQcb/QcPJvxe94/9RZuLQuF1+To4WqQcRvc+3J3gLCPIPgDKzGLG6zmQLfH3nn25heRuDNkS2KR4I8A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.20.0" + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" } }, "babel-plugin-polyfill-regenerator": { @@ -21681,12 +20269,12 @@ } }, "babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", + "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^27.4.0", + "babel-plugin-jest-hoist": "^29.2.0", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -21732,9 +20320,9 @@ "dev": true }, "bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==" + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" }, "binary-extensions": { "version": "2.2.0", @@ -21753,25 +20341,27 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "requires": { - "bytes": "3.1.1", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -21782,22 +20372,30 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } } } }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, "bootstrap": { @@ -21903,15 +20501,14 @@ } }, "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" } }, "bs58": { @@ -21972,7 +20569,7 @@ "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" }, "buffer-from": { "version": "1.1.2", @@ -21982,7 +20579,7 @@ "buffer-to-arraybuffer": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", - "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=" + "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==" }, "buffer-xor": { "version": "1.0.3", @@ -22025,50 +20622,27 @@ } }, "bytes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, - "cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - } + "cacheable-lookup": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", + "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==" }, "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", + "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" }, "dependencies": { "get-stream": { @@ -22083,11 +20657,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" } } }, @@ -22111,19 +20680,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, - "camelcase-keys": { - "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, - "optional": true, - "peer": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, "camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", @@ -22142,9 +20698,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001303", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz", - "integrity": "sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==" + "version": "1.0.30001426", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001426.tgz", + "integrity": "sha512-n7cosrHLl8AWt0wwZw/PJZgUg3lV0gk9LMI7ikGJwhyhgsd2Nb65vKvmSexCqq/J7rbH3mFG6yZZiPR5dLPW5A==" }, "caseless": { "version": "0.12.0", @@ -22181,14 +20737,14 @@ "dev": true }, "chart.js": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.0.tgz", - "integrity": "sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.0.1.tgz", + "integrity": "sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==" }, "chartjs-adapter-luxon": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.1.0.tgz", - "integrity": "sha512-CS+xBWEyXYVLBZ3dSY/MwlSXhz8er4JjkApazY84ft/++oOLsmkt6TaXBCsUFudum7QdoYmpxiL/gSp20+emkw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.0.tgz", + "integrity": "sha512-TPqS8S7aw4a07LhFzG5DZU6Kduk1xFkaGTn8y/gfhBRvfyCkqnwFqfXqG9Gl+gmnj5DRXgPscApJUE6bsgzKjQ==", "requires": {} }, "check-error": { @@ -22231,14 +20787,6 @@ } } }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "optional": true, - "peer": true - }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -22246,9 +20794,9 @@ "dev": true }, "ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", "dev": true }, "cids": { @@ -22294,18 +20842,10 @@ "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==" }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "optional": true, - "peer": true - }, "clipboard": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.9.tgz", - "integrity": "sha512-JK1AIxL35mdvQoP50gelg1Gsx6wMsfjtMCzFAdePdrGEHaqTnNAz3qZg4vhmLQDlpTqiVgj7GRzmpIDsAcdvRA==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -22340,9 +20880,9 @@ } }, "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "requires": { "mimic-response": "^1.0.0" } @@ -22350,7 +20890,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, "collect-v8-coverage": { @@ -22372,18 +20912,10 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "optional": true, - "peer": true - }, "colord": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.1.tgz", - "integrity": "sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", + "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", "dev": true }, "colorette": { @@ -22418,14 +20950,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true, - "peer": true - }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -22465,14 +20989,14 @@ } }, "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "cookiejar": { "version": "2.1.3", @@ -22480,87 +21004,38 @@ "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, "copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", "requires": { "toggle-selection": "^1.0.6" } }, "copy-webpack-plugin": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.1.tgz", - "integrity": "sha512-nr81NhCAIpAWXGCK5thrKmfCQ6GDY0L5RN0U+BnIn/7Us55+UCex5ANNsNKmIVtDRnk0Ecf+/kzp9SUVrrBMLg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", "dev": true, "requires": { - "fast-glob": "^3.2.7", + "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", - "globby": "^12.0.2", + "globby": "^13.1.1", "normalize-path": "^3.0.0", "schema-utils": "^4.0.0", "serialize-javascript": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - } } }, "core-js": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz", - "integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==" + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", + "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==" }, "core-js-compat": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.3.tgz", - "integrity": "sha512-c8M5h0IkNZ+I92QhIpuSijOxGAcj3lgpsWdkCqmUTZNwidujF4r3pi6x1DCN+Vcs5qTS2XWWMfWSuCqyupX8gw==", + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.1.tgz", + "integrity": "sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw==", "requires": { - "browserslist": "^4.19.1", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } + "browserslist": "^4.21.3" } }, "core-util-is": { @@ -22591,13 +21066,9 @@ } }, "crc-32": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", - "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.3.1" - } + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" }, "create-ecdh": { "version": "4.0.4", @@ -22683,20 +21154,12 @@ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" }, - "css-color-names": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", - "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==", - "dev": true - }, "css-declaration-sorter": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz", - "integrity": "sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", + "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", "dev": true, - "requires": { - "timsort": "^0.3.0" - } + "requires": {} }, "css-loader": { "version": "5.2.7", @@ -22716,17 +21179,6 @@ "semver": "^7.3.5" }, "dependencies": { - "loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -22750,77 +21202,59 @@ } }, "css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz", + "integrity": "sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==", "dev": true, "requires": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", + "cssnano": "^5.1.8", + "jest-worker": "^29.1.2", + "postcss": "^8.4.17", "schema-utils": "^4.0.0", "serialize-javascript": "^6.0.0", "source-map": "^0.6.1" }, "dependencies": { - "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "jest-worker": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.1.2.tgz", + "integrity": "sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.3" + "@types/node": "*", + "jest-util": "^29.1.2", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "has-flag": "^4.0.0" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "dev": true, "requires": { "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" } }, "css-to-react-native": { @@ -22841,20 +21275,12 @@ "requires": { "mdn-data": "2.0.14", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "css-what": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", - "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, "cssesc": { @@ -22869,58 +21295,57 @@ "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" }, "cssnano": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.0.9.tgz", - "integrity": "sha512-Y4olTKBKsPKl5izpcXHRDiB/1rVdbIDM4qVXgEKBt466kYT42SEEsnCYOQFFXzEkUYV8pJNCII9JKzb8KfDk+g==", + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.12.tgz", + "integrity": "sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ==", "dev": true, "requires": { - "cssnano-preset-default": "^5.1.5", - "is-resolvable": "^1.1.0", + "cssnano-preset-default": "^5.2.12", "lilconfig": "^2.0.3", "yaml": "^1.10.2" } }, "cssnano-preset-default": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.5.tgz", - "integrity": "sha512-fF00UI+d3PWkGfMd62geqmoUe5h+LOhGE2GH4Fqq3beNKdCU1LWwLUyIcu4/A72lWv0737cHey5zhhWw3rW0sA==", - "dev": true, - "requires": { - "css-declaration-sorter": "^6.0.3", - "cssnano-utils": "^2.0.1", - "postcss-calc": "^8.0.0", - "postcss-colormin": "^5.2.1", - "postcss-convert-values": "^5.0.2", - "postcss-discard-comments": "^5.0.1", - "postcss-discard-duplicates": "^5.0.1", - "postcss-discard-empty": "^5.0.1", - "postcss-discard-overridden": "^5.0.1", - "postcss-merge-longhand": "^5.0.2", - "postcss-merge-rules": "^5.0.2", - "postcss-minify-font-values": "^5.0.1", - "postcss-minify-gradients": "^5.0.3", - "postcss-minify-params": "^5.0.1", - "postcss-minify-selectors": "^5.1.0", - "postcss-normalize-charset": "^5.0.1", - "postcss-normalize-display-values": "^5.0.1", - "postcss-normalize-positions": "^5.0.1", - "postcss-normalize-repeat-style": "^5.0.1", - "postcss-normalize-string": "^5.0.1", - "postcss-normalize-timing-functions": "^5.0.1", - "postcss-normalize-unicode": "^5.0.1", - "postcss-normalize-url": "^5.0.2", - "postcss-normalize-whitespace": "^5.0.1", - "postcss-ordered-values": "^5.0.2", - "postcss-reduce-initial": "^5.0.1", - "postcss-reduce-transforms": "^5.0.1", - "postcss-svgo": "^5.0.3", - "postcss-unique-selectors": "^5.0.1" + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", + "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", + "dev": true, + "requires": { + "css-declaration-sorter": "^6.3.0", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.0", + "postcss-convert-values": "^5.1.2", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.6", + "postcss-merge-rules": "^5.1.2", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.3", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" } }, "cssnano-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", - "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", "dev": true, "requires": {} }, @@ -22934,9 +21359,9 @@ } }, "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true }, "cssstyle": { @@ -22974,14 +21399,14 @@ } }, "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "dev": true, "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" } }, "debug": { @@ -22997,51 +21422,36 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "optional": true, - "peer": 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, - "optional": true, - "peer": true - } - } - }, "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", "dev": true }, "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } } }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, "deep-eql": { @@ -23065,9 +21475,9 @@ "dev": true }, "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, "deferred-leveldown": { "version": "1.2.2", @@ -23095,19 +21505,6 @@ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true, - "peer": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -23118,9 +21515,9 @@ } }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-browser": { "version": "5.2.1", @@ -23134,9 +21531,9 @@ "dev": true }, "diff-sequences": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", + "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", "dev": true }, "diffie-hellman": { @@ -23180,9 +21577,9 @@ } }, "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", "dev": true, "requires": { "domelementtype": "^2.0.1", @@ -23196,32 +21593,24 @@ "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true }, "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "dev": true, "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } + "webidl-conversions": "^7.0.0" } }, "domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", "dev": true, "requires": { "domelementtype": "^2.2.0" @@ -23243,11 +21632,6 @@ "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", "integrity": "sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA==" }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -23260,12 +21644,12 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.55", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.55.tgz", - "integrity": "sha512-AoCDEVElLY8mwe4TuDDkr1jxvSh/Ih5PFlEXCpmwFkq9JOXn4K58CScgBl+R1ghFW9cPJ7VeWo30nAHSRCe6rw==" + "version": "1.4.256", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", + "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==" }, "elliptic": { "version": "6.5.4", @@ -23289,9 +21673,9 @@ } }, "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true }, "emoji-regex": { @@ -23309,7 +21693,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "encoding": { "version": "0.1.13", @@ -23342,9 +21726,9 @@ } }, "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -23357,28 +21741,12 @@ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "optional": true, - "peer": true - }, "envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "optional": true, - "peer": true - }, "errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -23440,19 +21808,19 @@ } }, "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" } }, "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -23464,6 +21832,11 @@ "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -23481,12 +21854,12 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "escodegen": { "version": "2.0.0", @@ -23510,7 +21883,7 @@ "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, "requires": { "prelude-ls": "~1.1.2", @@ -23534,20 +21907,13 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, "requires": { "prelude-ls": "~1.1.2" @@ -23556,13 +21922,15 @@ } }, "eslint": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz", - "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", + "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.9.2", + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -23572,18 +21940,21 @@ "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", + "espree": "^9.4.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -23594,8 +21965,7 @@ "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "dependencies": { "ansi-styles": { @@ -23677,6 +22047,16 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, "globals": { "version": "13.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", @@ -23701,6 +22081,15 @@ "argparse": "^2.0.1" } }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -23710,6 +22099,21 @@ "brace-expansion": "^1.1.7" } }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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 + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23926,9 +22330,9 @@ } }, "eslint-plugin-promise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", - "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", "dev": true, "requires": {} }, @@ -23966,22 +22370,14 @@ "dev": true }, "espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", "dev": true, "requires": { - "acorn": "^8.7.1", + "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - } } }, "esprima": { @@ -24039,7 +22435,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "eth-block-tracker": { "version": "4.4.3", @@ -24057,7 +22453,7 @@ "eth-ens-namehash": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", - "integrity": "sha1-IprEbsqG1S4MmR58sq74P/D2i88=", + "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", "requires": { "idna-uts46-hx": "^2.3.1", "js-sha3": "^0.5.7" @@ -24066,7 +22462,7 @@ "js-sha3": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==" } } }, @@ -24191,9 +22587,9 @@ } }, "eth-net-props": { - "version": "1.0.40", - "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.40.tgz", - "integrity": "sha512-HSZQ+lmEqSTV9B68gjoBHL5seJsahiDyvlbXtG601+BzXhUIxVwfkBvhjbw5LQgKDBlDwwPryBqfKXAAU3rXPA==", + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.41.tgz", + "integrity": "sha512-4qUNJU8xyqV53Lr+5JMnCUoknL/IIQ8Zpk1CKV/8h7tvtdrqbvnvJNb3IC0nUcyf4f0tNRTQnSWslut7XRwpRA==", "requires": { "chai": "^4.2.0" } @@ -24414,9 +22810,9 @@ } }, "ethereumjs-util": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.3.tgz", - "integrity": "sha512-y+82tEbyASO0K0X1/SRhbJJoAlfcvq8JbrG4a5cjrOks7HS/36efU/0j2flxCPOUM++HFahk33kr/ZxyC4vNuw==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "requires": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", @@ -24426,9 +22822,9 @@ }, "dependencies": { "@types/bn.js": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", - "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", "requires": { "@types/node": "*" } @@ -24514,7 +22910,7 @@ "ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk=", + "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", "requires": { "bn.js": "4.11.6", "number-to-bn": "1.7.0" @@ -24523,7 +22919,7 @@ "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" } } }, @@ -24570,71 +22966,60 @@ "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - } } }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true }, - "exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" - }, "expect": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", + "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" + "@jest/expect-utils": "^29.3.1", + "jest-get-type": "^29.2.0", + "jest-matcher-utils": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1" } }, "express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.1", + "body-parser": "1.20.1", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.1", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.6", + "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -24648,15 +23033,23 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } }, "safe-buffer": { "version": "5.2.1", @@ -24666,17 +23059,17 @@ } }, "ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", "requires": { - "type": "^2.5.0" + "type": "^2.7.2" }, "dependencies": { "type": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", - "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" } } }, @@ -24759,9 +23152,9 @@ } }, "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "requires": { "bser": "2.1.1" @@ -24786,17 +23179,6 @@ "schema-utils": "^3.0.0" }, "dependencies": { - "loader-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.1.tgz", - "integrity": "sha512-g4miPa9uUrZz4iElkaVJgDFwKJGh8aQGM7pUL4ejXl6cu7kSb30seQOVGNMP6sW8j7DW77X68hJZ+GM7UGhXeQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -24820,16 +23202,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -24844,7 +23226,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -24958,6 +23340,11 @@ "mime-types": "^2.1.12" } }, + "form-data-encoder": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz", + "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==" + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -24972,7 +23359,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-extra": { "version": "4.0.3", @@ -24984,17 +23371,6 @@ "universalify": "^0.1.0" } }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.0.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -25018,36 +23394,6 @@ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, - "gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - } - }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "globule": "^1.0.0" - } - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -25079,21 +23425,10 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "optional": true, - "peer": true - }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" }, "get-symbol-description": { "version": "1.0.0", @@ -25156,15 +23491,14 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", - "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", "dev": true, "requires": { - "array-union": "^3.0.1", "dir-glob": "^3.0.1", - "fast-glob": "^3.2.7", - "ignore": "^5.1.9", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^4.0.0" }, @@ -25177,37 +23511,6 @@ } } }, - "globule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz", - "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, "good-listener": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", @@ -25217,21 +23520,23 @@ } }, "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz", + "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==", + "requires": { + "@sindresorhus/is": "^4.6.0", + "@szmarczak/http-timer": "^5.0.1", + "@types/cacheable-request": "^6.0.2", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^6.0.4", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "form-data-encoder": "1.7.1", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^2.0.0" } }, "graceful-fs": { @@ -25239,6 +23544,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -25253,14 +23564,6 @@ "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, - "optional": true, - "peer": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -25279,24 +23582,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" - }, "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, "has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", @@ -25305,14 +23595,6 @@ "has-symbols": "^1.0.2" } }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true, - "peer": true - }, "hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -25340,9 +23622,9 @@ } }, "highlight.js": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.4.0.tgz", - "integrity": "sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA==" + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==" }, "hmac-drbg": { "version": "1.0.1", @@ -25362,21 +23644,13 @@ "react-is": "^16.7.0" } }, - "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, - "optional": true, - "peer": true - }, "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, "requires": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^2.0.0" } }, "html-escaper": { @@ -25391,32 +23665,28 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } } }, "http-https": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", - "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=" - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } + "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==" }, "http-signature": { "version": "1.2.0", @@ -25428,15 +23698,31 @@ "sshpk": "^1.7.0" } }, + "http2-wrapper": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz", + "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "dependencies": { + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + } + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { "agent-base": "6", @@ -25449,17 +23735,6 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "^2.0.0" - } - }, "humps": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", @@ -25491,7 +23766,7 @@ "punycode": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=" + "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==" } } }, @@ -25597,22 +23872,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "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, - "optional": true, - "peer": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true, - "optional": true, - "peer": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -25639,30 +23898,16 @@ } }, "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true, - "optional": true, - "peer": true - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -25775,14 +24020,6 @@ "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true, - "optional": true, - "peer": true - }, "is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", @@ -25811,15 +24048,11 @@ "has-tostringtag": "^1.0.0" } }, - "is-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==" - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true }, "is-plain-object": { "version": "2.0.4", @@ -25845,17 +24078,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" - }, "is-shared-array-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", @@ -25937,9 +24159,9 @@ "dev": true }, "istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "requires": { "@babel/core": "^7.12.3", @@ -25986,82 +24208,65 @@ "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, "requires": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, "jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", + "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", "dev": true, "requires": { - "@jest/core": "^27.4.7", + "@jest/core": "^29.3.1", + "@jest/types": "^29.3.1", "import-local": "^3.0.2", - "jest-cli": "^27.4.7" + "jest-cli": "^29.3.1" } }, "jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", + "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", "dev": true, "requires": { - "@jest/types": "^27.4.2", "execa": "^5.0.0", - "throat": "^6.0.1" + "p-limit": "^3.1.0" } }, "jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", + "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^29.3.1", + "@jest/expect": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.4.6", "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", + "jest-each": "^29.3.1", + "jest-matcher-utils": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-runtime": "^29.3.1", + "jest-snapshot": "^29.3.1", + "jest-util": "^29.3.1", + "p-limit": "^3.1.0", + "pretty-format": "^29.3.1", "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" + "stack-utils": "^2.0.3" }, "dependencies": { "ansi-styles": { @@ -26116,23 +24321,23 @@ } }, "jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", + "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", "dev": true, "requires": { - "@jest/core": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/core": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/types": "^29.3.1", "chalk": "^4.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-config": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", "prompts": "^2.0.1", - "yargs": "^16.2.0" + "yargs": "^17.3.1" }, "dependencies": { "ansi-styles": { @@ -26187,33 +24392,33 @@ } }, "jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", + "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", "dev": true, "requires": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.3.1", + "@jest/types": "^29.3.1", + "babel-jest": "^29.3.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.3.1", + "jest-environment-node": "^29.3.1", + "jest-get-type": "^29.2.0", + "jest-regex-util": "^29.2.0", + "jest-resolve": "^29.3.1", + "jest-runner": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" + "parse-json": "^5.2.0", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "dependencies": { "ansi-styles": { @@ -26268,15 +24473,15 @@ } }, "jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", + "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "diff-sequences": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" }, "dependencies": { "ansi-styles": { @@ -26331,25 +24536,25 @@ } }, "jest-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", + "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", + "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" + "jest-get-type": "^29.2.0", + "jest-util": "^29.3.1", + "pretty-format": "^29.3.1" }, "dependencies": { "ansi-styles": { @@ -26404,130 +24609,83 @@ } }, "jest-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz", + "integrity": "sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", - "jsdom": "^16.6.0" + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1", + "jsdom": "^20.0.0" } }, "jest-environment-node": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", + "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" } }, "jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", + "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", "dev": true }, "jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", + "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "@types/graceful-fs": "^4.1.2", + "@jest/types": "^29.3.1", + "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.2.0", + "jest-util": "^29.3.1", + "jest-worker": "^29.3.1", "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "throat": "^6.0.1" + "walker": "^1.0.8" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "jest-worker": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", + "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.3.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -26536,25 +24694,25 @@ } }, "jest-leak-detector": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", + "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", "dev": true, "requires": { - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" } }, "jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", + "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-diff": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" }, "dependencies": { "ansi-styles": { @@ -26609,18 +24767,18 @@ } }, "jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", + "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", + "pretty-format": "^29.3.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -26677,13 +24835,14 @@ } }, "jest-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", + "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "@types/node": "*" + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-util": "^29.3.1" } }, "jest-pnp-resolver": { @@ -26694,24 +24853,23 @@ "requires": {} }, "jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", + "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", "dev": true }, "jest-resolve": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", + "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", "dev": true, "requires": { - "@jest/types": "^27.4.2", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" @@ -26769,44 +24927,42 @@ } }, "jest-resolve-dependencies": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", + "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" + "jest-regex-util": "^29.2.0", + "jest-snapshot": "^29.3.1" } }, "jest-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", + "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", "dev": true, "requires": { - "@jest/console": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^29.3.1", + "@jest/environment": "^29.3.1", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.2.0", + "jest-environment-node": "^29.3.1", + "jest-haste-map": "^29.3.1", + "jest-leak-detector": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-resolve": "^29.3.1", + "jest-runtime": "^29.3.1", + "jest-util": "^29.3.1", + "jest-watcher": "^29.3.1", + "jest-worker": "^29.3.1", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "dependencies": { "ansi-styles": { @@ -26849,6 +25005,39 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "jest-worker": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", + "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.3.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -26861,31 +25050,31 @@ } }, "jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", + "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", + "dev": true, + "requires": { + "@jest/environment": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/globals": "^29.3.1", + "@jest/source-map": "^29.2.0", + "@jest/test-result": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-regex-util": "^29.2.0", + "jest-resolve": "^29.3.1", + "jest-snapshot": "^29.3.1", + "jest-util": "^29.3.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -26941,44 +25130,36 @@ } } }, - "jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, "jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", + "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", "dev": true, "requires": { - "@babel/core": "^7.7.2", + "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__traverse": "^7.0.4", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.3.1", + "@jest/transform": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/babel__traverse": "^7.0.6", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "expect": "^29.3.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.3.1", + "jest-get-type": "^29.2.0", + "jest-haste-map": "^29.3.1", + "jest-matcher-utils": "^29.3.1", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", - "semver": "^7.3.2" + "pretty-format": "^29.3.1", + "semver": "^7.3.5" }, "dependencies": { "ansi-styles": { @@ -27022,9 +25203,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -27042,16 +25223,16 @@ } }, "jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", + "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" }, "dependencies": { @@ -27107,17 +25288,17 @@ } }, "jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", + "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^29.3.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", + "jest-get-type": "^29.2.0", "leven": "^3.1.0", - "pretty-format": "^27.4.6" + "pretty-format": "^29.3.1" }, "dependencies": { "ansi-styles": { @@ -27178,17 +25359,18 @@ } }, "jest-watcher": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", + "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", "dev": true, "requires": { - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/test-result": "^29.3.1", + "@jest/types": "^29.3.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.4.2", + "emittery": "^0.13.1", + "jest-util": "^29.3.1", "string-length": "^4.0.1" }, "dependencies": { @@ -27244,9 +25426,9 @@ } }, "jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "requires": { "@types/node": "*", @@ -27272,17 +25454,20 @@ } }, "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.2.tgz", + "integrity": "sha512-/e7ulNIEEYk1Z/l4X0vpxGt+B/dNsV8ghOPAWZaJs8pkGvsSC0tm33aMGylXcj/U7y4IcvwtMXPMyBFZn/gK9A==" }, - "js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true, - "optional": true, - "peer": true + "js-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==" + }, + "js-sdsl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", + "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", + "dev": true }, "js-sha3": { "version": "0.8.0", @@ -27310,56 +25495,67 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", "dev": true, "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", + "abab": "^2.0.6", + "acorn": "^8.7.1", "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", + "cssom": "^0.5.0", "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "parse5": "^7.0.0", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.0.0", "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" }, "dependencies": { - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } } } }, @@ -27369,15 +25565,9 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -27439,17 +25629,14 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "requires": { "graceful-fs": "^4.1.6" } @@ -27481,11 +25668,11 @@ } }, "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", "requires": { - "json-buffer": "3.0.0" + "json-buffer": "3.0.1" } }, "keyvaluestorage-interface": { @@ -27641,9 +25828,9 @@ } }, "lilconfig": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", - "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", "dev": true }, "lines-and-columns": { @@ -27659,25 +25846,14 @@ "dev": true }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } + "json5": "^2.1.2" } }, "locate-path": { @@ -27758,7 +25934,7 @@ "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, "lodash.merge": { @@ -27794,7 +25970,7 @@ "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, "loose-envify": { @@ -27806,9 +25982,9 @@ } }, "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==" }, "lru-cache": { "version": "6.0.0", @@ -27825,9 +26001,9 @@ "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" }, "luxon": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.4.0.tgz", - "integrity": "sha512-w+NAwWOUL5hO0SgwOHsMBAmZ15SoknmQXhSO0hIbJCAmPKSsGeK8MlmhYh2w6Iib38IxN2M+/ooXWLbeis7GuA==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.1.tgz", + "integrity": "sha512-Ah6DloGmvseB/pX1cAmjbFvyU/pKuwQMQqz7d0yvuDlVYLTs2WeDHQMpC8tGjm1da+BriHROW/OEIT/KfYg6xw==" }, "make-dir": { "version": "3.1.0", @@ -27838,32 +26014,6 @@ "semver": "^6.0.0" } }, - "make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - } - }, "makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -27873,14 +26023,6 @@ "tmpl": "1.0.5" } }, - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "optional": true, - "peer": true - }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -27900,7 +26042,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "memdown": { "version": "1.4.1", @@ -27925,78 +26067,10 @@ } } }, - "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@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": { - "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, - "optional": true, - "peer": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "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, - "optional": true, - "peer": true - } - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "merge-stream": { "version": "2.0.0", @@ -28076,7 +26150,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromatch": { "version": "4.0.4", @@ -28110,16 +26184,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.50.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -28141,62 +26215,13 @@ "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, - "optional": true, - "peer": true - }, "mini-css-extract-plugin": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz", - "integrity": "sha512-YseMB8cs8U/KCaAGQoqYmfUuhhGW0a9p9XvWXrxVOkE3/IiISTLw4ALNt7JR5B2eYauFM+PQGSbXMDmVbR7Tfw==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", + "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", "dev": true, "requires": { "schema-utils": "^4.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - } } }, "minimalistic-assert": { @@ -28210,9 +26235,9 @@ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -28223,100 +26248,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, - "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, - "optional": true, - "peer": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -28325,7 +26256,7 @@ "mkdirp-promise": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", - "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=", + "integrity": "sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==", "requires": { "mkdirp": "*" } @@ -28336,9 +26267,9 @@ "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, "moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "ms": { "version": "2.1.2", @@ -28383,18 +26314,10 @@ } } }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true, - "optional": true, - "peer": true - }, "nano-json-stream-parser": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", - "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=" + "integrity": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==" }, "nanoassert": { "version": "1.1.0", @@ -28402,9 +26325,9 @@ "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" }, "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true }, "nanomorph": { @@ -28422,9 +26345,9 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "neo-async": { "version": "2.6.2", @@ -28433,9 +26356,9 @@ "dev": true }, "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node-addon-api": { "version": "2.0.2", @@ -28471,72 +26394,6 @@ } } }, - "node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "dependencies": { - "gauge": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", - "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ansi-regex": "^5.0.1", - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - } - }, - "npmlog": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", - "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.0", - "set-blocking": "^2.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", @@ -28545,136 +26402,13 @@ "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, - "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" - }, - "node-sass": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.1.tgz", - "integrity": "sha512-uMy+Xt29NlqKCFdFRZyXKOTqGt+QaKHexv9STj2WeLottnlqZEEWx6Bj0MXNthmFRRdM/YwyNo/8Tr46TOM0jQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "meow": "^9.0.0", - "nan": "^2.13.2", - "node-gyp": "^8.4.1", - "npmlog": "^5.0.0", - "request": "^2.88.0", - "sass-graph": "4.0.0", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "abbrev": "1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "optional": true, - "peer": true - } - } + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "normalize-path": { "version": "3.0.0", @@ -28691,8 +26425,7 @@ "normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" }, "npm-run-path": { "version": "4.0.1", @@ -28703,24 +26436,10 @@ "path-key": "^3.0.0" } }, - "npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "nth-check": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", - "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, "requires": { "boolbase": "^1.0.0" @@ -28729,7 +26448,7 @@ "number-to-bn": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", + "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", "requires": { "bn.js": "4.11.6", "strip-hex-prefix": "1.0.0" @@ -28738,7 +26457,7 @@ "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" } } }, @@ -28748,9 +26467,9 @@ "integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=" }, "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", "dev": true }, "oauth-sign": { @@ -28807,15 +26526,15 @@ "oboe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", - "integrity": "sha1-VVQoTFQ6ImbXo48X4HOCH73jk80=", + "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", "requires": { "http-https": "^1.0.0" } }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -28857,14 +26576,9 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" }, "p-limit": { "version": "3.1.0", @@ -28901,25 +26615,6 @@ } } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "requires": { - "p-finally": "^1.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -28964,10 +26659,21 @@ } }, "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "requires": { + "entities": "^4.3.0" + }, + "dependencies": { + "entities": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.1.tgz", + "integrity": "sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==", + "dev": true + } + } }, "parseurl": { "version": "1.3.3", @@ -28997,17 +26703,18 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/path-parser/-/path-parser-4.2.0.tgz", - "integrity": "sha512-MPPZiWTTp2I72VXmGQQfsn2ohrbd9QTbZSLYNS+HXsnQ37VbiLR/szO2R7DHaZA1V1scYxuxgyQerj+6kMTXtA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/path-parser/-/path-parser-6.1.0.tgz", + "integrity": "sha512-nAB6J73z2rFcQP+870OHhpkHFj5kO4rPLc2Ol4Y3Ale7F6Hk1/cPKp7cQ8RznKF8FOSvu+YR9Xc6Gafk7DlpYA==", "requires": { - "search-params": "2.1.3" + "search-params": "3.0.0", + "tslib": "^1.10.0" } }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "path-type": { "version": "4.0.0", @@ -29043,6 +26750,11 @@ "phoenix_html": { "version": "file:../../../deps/phoenix_html" }, + "photoswipe": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.4.tgz", + "integrity": "sha512-SN+RWHqxJvdwzXJsh8KrG+ajjPpdTX5HpKglEd0k9o6o5fW+QHPkW8//Bo11MB+NQwTa/hFw8BDv2EdxiDXjNw==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -29065,9 +26777,9 @@ "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew==" }, "pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true }, "pngjs": { @@ -29081,90 +26793,91 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", "dev": true, "requires": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "postcss-calc": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz", - "integrity": "sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" } }, "postcss-colormin": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.1.tgz", - "integrity": "sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", + "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", "dev": true, "requires": { "browserslist": "^4.16.6", "caniuse-api": "^3.0.0", "colord": "^2.9.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-convert-values": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz", - "integrity": "sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", + "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" } }, "postcss-discard-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", - "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", "dev": true, "requires": {} }, "postcss-discard-duplicates": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", - "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", "dev": true, "requires": {} }, "postcss-discard-empty": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", - "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", "dev": true, "requires": {} }, "postcss-discard-overridden": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", - "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", "dev": true, "requires": {} }, "postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", + "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", "dev": true, "requires": { "cosmiconfig": "^7.0.0", "klona": "^2.0.5", - "semver": "^7.3.5" + "semver": "^7.3.8" }, "dependencies": { "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -29173,69 +26886,64 @@ } }, "postcss-merge-longhand": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.2.tgz", - "integrity": "sha512-BMlg9AXSI5G9TBT0Lo/H3PfUy63P84rVz3BjCFE9e9Y9RXQZD3+h3YO1kgTNsNJy7bBc1YQp8DmSnwLIW5VPcw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", + "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", "dev": true, "requires": { - "css-color-names": "^1.0.1", - "postcss-value-parser": "^4.1.0", - "stylehacks": "^5.0.1" + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" } }, "postcss-merge-rules": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.2.tgz", - "integrity": "sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", + "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", "dev": true, "requires": { "browserslist": "^4.16.6", "caniuse-api": "^3.0.0", - "cssnano-utils": "^2.0.1", - "postcss-selector-parser": "^6.0.5", - "vendors": "^1.0.3" + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" } }, "postcss-minify-font-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz", - "integrity": "sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz", - "integrity": "sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", "dev": true, "requires": { "colord": "^2.9.1", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz", - "integrity": "sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", + "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", "dev": true, "requires": { - "alphanum-sort": "^1.0.2", - "browserslist": "^4.16.0", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0", - "uniqs": "^2.0.0" + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz", - "integrity": "sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", "dev": true, "requires": { - "alphanum-sort": "^1.0.2", "postcss-selector-parser": "^6.0.5" } }, @@ -29276,124 +26984,119 @@ } }, "postcss-normalize-charset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", - "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", "dev": true, "requires": {} }, "postcss-normalize-display-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz", - "integrity": "sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz", - "integrity": "sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz", - "integrity": "sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz", - "integrity": "sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz", - "integrity": "sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz", - "integrity": "sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", + "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", "dev": true, "requires": { - "browserslist": "^4.16.0", - "postcss-value-parser": "^4.1.0" + "browserslist": "^4.16.6", + "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-url": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.2.tgz", - "integrity": "sha512-k4jLTPUxREQ5bpajFQZpx8bCF2UrlqOTzP9kEqcEnOfwsRshWs2+oAFIHfDQB8GO2PaUaSE0NlTAYtbluZTlHQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", "dev": true, "requires": { - "is-absolute-url": "^3.0.3", "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz", - "integrity": "sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-ordered-values": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz", - "integrity": "sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" } }, "postcss-reduce-initial": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz", - "integrity": "sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", + "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", "dev": true, "requires": { - "browserslist": "^4.16.0", + "browserslist": "^4.16.6", "caniuse-api": "^3.0.0" } }, "postcss-reduce-transforms": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz", - "integrity": "sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "postcss-value-parser": "^4.2.0" } }, "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -29401,24 +27104,22 @@ } }, "postcss-svgo": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.3.tgz", - "integrity": "sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0", + "postcss-value-parser": "^4.2.0", "svgo": "^2.7.0" } }, "postcss-unique-selectors": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz", - "integrity": "sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", "dev": true, "requires": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5", - "uniqs": "^2.0.0" + "postcss-selector-parser": "^6.0.5" } }, "postcss-value-parser": { @@ -29442,20 +27143,15 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, "pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.0.0", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "dependencies": { "ansi-styles": { @@ -29465,18 +27161,13 @@ "dev": true }, "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true } } }, - "printj": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", - "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==" - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -29487,26 +27178,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true, - "optional": true, - "peer": true - }, - "promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - } - }, "promise-to-callback": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/promise-to-callback/-/promise-to-callback-1.0.0.tgz", @@ -29634,7 +27305,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" }, "isarray": { "version": "2.0.5", @@ -29723,9 +27394,9 @@ } }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "query-string": { "version": "5.1.1", @@ -29742,20 +27413,18 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "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, - "optional": true, - "peer": true - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -29779,12 +27448,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.1", - "http-errors": "1.8.1", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -29815,106 +27484,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "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, - "optional": true, - "peer": true - } - } - }, - "read-pkg-up": { - "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, - "optional": true, - "peer": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "optional": true, - "peer": 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, - "optional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "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, - "optional": true, - "peer": true - }, - "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, - "optional": true, - "peer": true - } - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -29934,36 +27503,24 @@ "picomatch": "^2.2.1" } }, - "rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "requires": { - "resolve": "^1.9.0" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, - "optional": true, - "peer": true, "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "resolve": "^1.20.0" } }, "reduce-reducers": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/reduce-reducers/-/reduce-reducers-0.4.3.tgz", - "integrity": "sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reduce-reducers/-/reduce-reducers-1.0.4.tgz", + "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "redux": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", - "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", "requires": { "@babel/runtime": "^7.9.2" } @@ -29975,9 +27532,9 @@ "dev": true }, "regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "requires": { "regenerate": "^1.4.2" @@ -29989,9 +27546,9 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", "dev": true, "requires": { "@babel/runtime": "^7.8.4" @@ -30004,29 +27561,29 @@ "dev": true }, "regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", "dev": true, "requires": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.0.0" } }, "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", "dev": true }, "regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -30035,7 +27592,7 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", "dev": true } } @@ -30094,6 +27651,12 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -30104,6 +27667,11 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -30134,21 +27702,20 @@ "dev": true }, "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "requires": { - "lowercase-keys": "^1.0.0" + "lowercase-keys": "^2.0.0" + }, + "dependencies": { + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } } }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, - "optional": true, - "peer": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -30214,9 +27781,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.49.8", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz", - "integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==", + "version": "1.56.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.2.tgz", + "integrity": "sha512-ciEJhnyCRwzlBCB+h5cCPM6ie/6f8HrhZMQOf5vlU60Y1bI1rx5Zb0vlDZvaycHsg/MqFfF1Eq2eokAa32iw8w==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -30224,59 +27791,10 @@ "source-map-js": ">=0.6.2 <2.0.0" } }, - "sass-graph": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.0.tgz", - "integrity": "sha512-WSO/MfXqKH7/TS8RdkCX3lVkPFQzCgbqdGsmSKq6tlPU+GpGEsa/5aW18JqItnqh+lPtcjifqdZ/VmiILkKckQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.3.0", - "yargs": "^17.2.1" - }, - "dependencies": { - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "optional": true, - "peer": true - }, - "yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", + "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", "dev": true, "requires": { "klona": "^2.0.4", @@ -30284,9 +27802,9 @@ } }, "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "requires": { "xmlchars": "^2.2.0" @@ -30302,14 +27820,44 @@ } }, "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "scrypt-js": { @@ -30317,32 +27865,10 @@ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, - "scss-tokenizer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.3.0.tgz", - "integrity": "sha512-14Zl9GcbBvOT9057ZKjpz5yPOyUWG2ojd9D5io28wHRYsOrs7U95Q+KNL87+32p8rc+LvDpbu/i9ZYjM9Q+FsQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "js-base64": "^2.4.3", - "source-map": "^0.7.1" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "search-params": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/search-params/-/search-params-2.1.3.tgz", - "integrity": "sha512-hHxU9ZGWpZ/lrFBIHndSnQae2in7ra+m+tBSoeAahSWDDgOgpZqs4bfaTZpljgNgAgTbjiQoJtZW6FKSsfEcDA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/search-params/-/search-params-3.0.0.tgz", + "integrity": "sha512-8CYNl/bjkEhXWbDTU/K7c2jQtrnqEffIPyOLMqygW/7/b+ym8UtQumcAZjOfMLjZKR6AxK5tOr9fChbQZCzPqg==" }, "secp256k1": { "version": "4.0.2", @@ -30370,23 +27896,23 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -30400,10 +27926,15 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -30421,14 +27952,14 @@ } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "servify": { @@ -30512,9 +28043,9 @@ } }, "signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "simple-concat": { @@ -30530,6 +28061,16 @@ "decompress-response": "^3.3.0", "once": "^1.3.1", "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "requires": { + "mimic-response": "^1.0.0" + } + } } }, "sisteransi": { @@ -30544,43 +28085,11 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "optional": true, - "peer": true - }, - "socks": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", - "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" - } - }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-js": { "version": "1.0.2", @@ -30596,56 +28105,8 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true, - "optional": true, - "peer": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" } }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true, - "optional": true, - "peer": true - }, "split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", @@ -30654,7 +28115,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "sshpk": { @@ -30673,17 +28134,6 @@ "tweetnacl": "~0.14.0" } }, - "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.1.1" - } - }, "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -30708,50 +28158,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "stream-browserify": { "version": "3.0.0", @@ -30776,7 +28185,7 @@ "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==" }, "string_decoder": { "version": "1.3.0", @@ -30861,17 +28270,6 @@ "is-hex-prefixed": "1.0.0" } }, - "strip-indent": { - "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, - "optional": true, - "peer": true, - "requires": { - "min-indent": "^1.0.0" - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -30902,49 +28300,22 @@ "supports-color": "^5.5.0" } }, - "stylehacks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", - "integrity": "sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==", - "dev": true, - "requires": { - "browserslist": "^4.16.0", - "postcss-selector-parser": "^6.0.4" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "stylehacks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", + "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "postcss-selector-parser": "^6.0.4" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" } }, "supports-preserve-symlinks-flag": { @@ -30968,15 +28339,15 @@ } }, "swarm-js": { - "version": "0.1.40", - "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.40.tgz", - "integrity": "sha512-yqiOCEoA4/IShXkY3WKwP5PvZhmoOOD8clsKA7EEcRILMkTEYHCQ21HDCAcVpmIxZq4LyZvWeRJ6quIyHk1caA==", + "version": "0.1.42", + "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.42.tgz", + "integrity": "sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==", "requires": { "bluebird": "^3.5.0", "buffer": "^5.0.5", "eth-lib": "^0.1.26", "fs-extra": "^4.0.2", - "got": "^7.1.0", + "got": "^11.8.5", "mime-types": "^2.1.16", "mkdirp-promise": "^5.0.1", "mock-fs": "^4.1.0", @@ -30985,6 +28356,19 @@ "xhr-request": "^1.0.1" }, "dependencies": { + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -30998,36 +28382,37 @@ "minipass": "^2.6.0" } }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", "requires": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" } }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, "minipass": { "version": "2.9.0", @@ -31047,22 +28432,22 @@ } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, "safe-buffer": { "version": "5.2.1", @@ -31083,14 +28468,6 @@ "yallist": "^3.1.1" } }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -31099,9 +28476,9 @@ } }, "sweetalert2": { - "version": "11.3.10", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.3.10.tgz", - "integrity": "sha512-/3nhG5QCREkPUndAbOF9h1IM7lgCIU/evsNXd/YUfa9eJ04M+hxksxIio8hhtH16UnWd2GvJ+zvFQ8H9hLRhsw==" + "version": "11.6.15", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.6.15.tgz", + "integrity": "sha512-FqMy1gRGHEI5G145NE5XSP059TziCJu9Xf9/mkki/aKu5pLNcYzjggOzKO5Ex10EBgAGDXQ99jyGfYYzGCYXRQ==" }, "symbol-tree": { "version": "3.2.4", @@ -31115,40 +28492,15 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, "terser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", - "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.7.2", "source-map-support": "~0.5.20" }, "dependencies": { @@ -31157,12 +28509,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true } } }, @@ -31190,12 +28536,6 @@ "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -31216,22 +28556,10 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", - "dev": true + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==" }, "tiny-emitter": { "version": "2.1.0", @@ -31249,11 +28577,6 @@ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -31266,7 +28589,7 @@ "toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, "toidentifier": { "version": "1.0.1", @@ -31274,44 +28597,34 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", "dev": true, "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } } }, "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "dev": true, "requires": { "punycode": "^2.1.1" } }, - "trim-newlines": { - "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, - "optional": true, - "peer": true - }, - "true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "glob": "^7.1.2" - } - }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -31445,34 +28758,6 @@ "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", "dev": true }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -31481,7 +28766,16 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } }, "uri-js": { "version": "4.4.1", @@ -31512,23 +28806,20 @@ } } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, "requires": { - "prepend-http": "^2.0.0" + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, "url-set-query": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", - "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=" - }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" + "integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==" }, "utf-8-validate": { "version": "5.0.7", @@ -31544,15 +28835,14 @@ "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, "util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "requires": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", "which-typed-array": "^1.1.2" } }, @@ -31564,48 +28854,22 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "v8-to-istanbul": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz", - "integrity": "sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "convert-source-map": "^1.6.0" } }, "varint": { @@ -31616,13 +28880,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", - "dev": true + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "verror": { "version": "1.10.0", @@ -31634,6 +28892,11 @@ "extsprintf": "^1.2.0" } }, + "viewerjs": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/viewerjs/-/viewerjs-1.11.1.tgz", + "integrity": "sha512-/VQ2zalHLZJOGIwlxOBtxagLZwNvU3Bf+nm692XlhNFxjBXRxpCVn+GeqmRFg9jK1Y2+Wf8PPGxZgTDN4pHXww==" + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -31644,12 +28907,12 @@ } }, "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", "dev": true, "requires": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^4.0.0" } }, "walker": { @@ -31662,9 +28925,9 @@ } }, "watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -31672,151 +28935,159 @@ } }, "web3": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.7.0.tgz", - "integrity": "sha512-n39O7QQNkpsjhiHMJ/6JY6TaLbdX+2FT5iGs8tb3HbIWOhPm4+a7UDbr5Lkm+gLa9aRKWesZs5D5hWyEvg4aJA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.1.tgz", + "integrity": "sha512-tAqFsQhGv340C9OgRJIuoScN7f7wa1tUvsnnDUMt9YE6J4gcm7TV2Uwv+KERnzvV+xgdeuULYpsioRRNKrUvoQ==", "requires": { - "web3-bzz": "1.7.0", - "web3-core": "1.7.0", - "web3-eth": "1.7.0", - "web3-eth-personal": "1.7.0", - "web3-net": "1.7.0", - "web3-shh": "1.7.0", - "web3-utils": "1.7.0" + "web3-bzz": "1.8.1", + "web3-core": "1.8.1", + "web3-eth": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-shh": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-bzz": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.7.0.tgz", - "integrity": "sha512-XPhTWUnZa8gnARfiqaag3jJ9+6+a66Li8OikgBUJoMUqPuQTCJPncTbGYqOJIfRFGavEAdlMnfYXx9lvgv2ZPw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.1.tgz", + "integrity": "sha512-dJJHS84nvpoxv6ijTMkdUSlRr5beCXNtx4UZcrFLHBva8dT63QEtKdLyDt2AyMJJdVzTCk78uir/6XtVWrdS6w==", "requires": { "@types/node": "^12.12.6", - "got": "9.6.0", + "got": "12.1.0", "swarm-js": "^0.1.40" }, "dependencies": { "@types/node": { - "version": "12.20.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.42.tgz", - "integrity": "sha512-aI3/oo5DzyiI5R/xAhxxRzfZlWlsbbqdgxfTPkqu/Zt+23GXiJvMCyPJT4+xKSXOnLqoL8jJYMLTwvK2M3a5hw==" + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" } } }, "web3-core": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.7.0.tgz", - "integrity": "sha512-U/CRL53h3T5KHl8L3njzCBT7fCaHkbE6BGJe3McazvFldRbfTDEHXkUJCyM30ZD0RoLi3aDfTVeFIusmEyCctA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.1.tgz", + "integrity": "sha512-LbRZlJH2N6nS3n3Eo9Y++25IvzMY7WvYnp4NM/Ajhh97dAdglYs6rToQ2DbL2RLvTYmTew4O/y9WmOk4nq9COw==", "requires": { - "@types/bn.js": "^4.11.5", + "@types/bn.js": "^5.1.0", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-requestmanager": "1.7.0", - "web3-utils": "1.7.0" + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-requestmanager": "1.8.1", + "web3-utils": "1.8.1" }, "dependencies": { + "@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { - "version": "12.20.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.42.tgz", - "integrity": "sha512-aI3/oo5DzyiI5R/xAhxxRzfZlWlsbbqdgxfTPkqu/Zt+23GXiJvMCyPJT4+xKSXOnLqoL8jJYMLTwvK2M3a5hw==" + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" } } }, "web3-core-helpers": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.7.0.tgz", - "integrity": "sha512-kFiqsZFHJliKF8VKZNjt2JvKu3gu7h3N1/ke3EPhdp9Li/rLmiyzFVr6ApryZ1FSjbSx6vyOkibG3m6xQ5EHJA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.1.tgz", + "integrity": "sha512-ClzNO6T1S1gifC+BThw0+GTfcsjLEY8T1qUp6Ly2+w4PntAdNtKahxWKApWJ0l9idqot/fFIDXwO3Euu7I0Xqw==", "requires": { - "web3-eth-iban": "1.7.0", - "web3-utils": "1.7.0" + "web3-eth-iban": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-core-method": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.7.0.tgz", - "integrity": "sha512-43Om+kZX8wU5u1pJ28TltF9e9pSTRph6b8wrOb6wgXAfPHqMulq6UTBJWjXXIRVN46Eiqv0nflw35hp9bbgnbA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.1.tgz", + "integrity": "sha512-oYGRodktfs86NrnFwaWTbv2S38JnpPslFwSSARwFv4W9cjbGUW3LDeA5MKD/dRY+ssZ5OaekeMsUCLoGhX68yA==", "requires": { - "@ethersproject/transactions": "^5.0.0-beta.135", - "web3-core-helpers": "1.7.0", - "web3-core-promievent": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-utils": "1.7.0" + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-core-promievent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.7.0.tgz", - "integrity": "sha512-xPH66XeC0K0k29GoRd0vyPQ07yxERPRd4yVPrbMzGAz/e9E4M3XN//XK6+PdfGvGw3fx8VojS+tNIMiw+PujbQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.1.tgz", + "integrity": "sha512-9mxqHlgB0MrZI4oUIRFkuoJMNj3E7btjrMv3sMer/Z9rYR1PfoSc1aAokw4rxKIcAh+ylVtd/acaB2HKB7aRPg==", "requires": { "eventemitter3": "4.0.4" } }, "web3-core-requestmanager": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.7.0.tgz", - "integrity": "sha512-rA3dBTBPrt+eIfTAQ2/oYNTN/2wbZaYNR3pFZGqG8+2oCK03+7oQyz4sWISKy/nYQhURh4GK01rs9sN4o/Tq9w==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.1.tgz", + "integrity": "sha512-x+VC2YPPwZ1khvqA6TA69LvfFCOZXsoUVOxmTx/vIN22PrY9KzKhxcE7pBSiGhmab1jtmRYXUbcQSVpAXqL8cw==", "requires": { "util": "^0.12.0", - "web3-core-helpers": "1.7.0", - "web3-providers-http": "1.7.0", - "web3-providers-ipc": "1.7.0", - "web3-providers-ws": "1.7.0" + "web3-core-helpers": "1.8.1", + "web3-providers-http": "1.8.1", + "web3-providers-ipc": "1.8.1", + "web3-providers-ws": "1.8.1" } }, "web3-core-subscriptions": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.7.0.tgz", - "integrity": "sha512-6giF8pyJrPmWrRpc2WLoVCvQdMMADp20ZpAusEW72axauZCNlW1XfTjs0i4QHQBfdd2lFp65qad9IuATPhuzrQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.1.tgz", + "integrity": "sha512-bmCMq5OeA3E2vZUh8Js1HcJbhwtsE+yeMqGC4oIZB3XsL5SLqyKLB/pU+qUYqQ9o4GdcrFTDPhPg1bgvf7p1Pw==", "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.0" + "web3-core-helpers": "1.8.1" } }, "web3-eth": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.7.0.tgz", - "integrity": "sha512-3uYwjMjn/MZjKIzXCt4YL9ja/k9X5shfa4lKparZhZE6uesmu+xmSmrEFXA/e9qcveF50jkV7frjkT8H+cLYtw==", - "requires": { - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-eth-abi": "1.7.0", - "web3-eth-accounts": "1.7.0", - "web3-eth-contract": "1.7.0", - "web3-eth-ens": "1.7.0", - "web3-eth-iban": "1.7.0", - "web3-eth-personal": "1.7.0", - "web3-net": "1.7.0", - "web3-utils": "1.7.0" + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.1.tgz", + "integrity": "sha512-LgyzbhFqiFRd8M8sBXoFN4ztzOnkeckl3H/9lH5ek7AdoRMhBg7tYpYRP3E5qkhd/q+yiZmcUgy1AF6NHrC1wg==", + "requires": { + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-accounts": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-eth-ens": "1.8.1", + "web3-eth-iban": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-eth-abi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.7.0.tgz", - "integrity": "sha512-heqR0bWxgCJwjWIhq2sGyNj9bwun5+Xox/LdZKe+WMyTSy0cXDXEAgv3XKNkXC4JqdDt/ZlbTEx4TWak4TRMSg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.1.tgz", + "integrity": "sha512-0mZvCRTIG0UhDhJwNQJgJxu4b4DyIpuMA0GTfqxqeuqzX4Q/ZvmoNurw0ExTfXaGPP82UUmmdkRi6FdZOx+C6w==", "requires": { - "@ethersproject/abi": "5.0.7", - "web3-utils": "1.7.0" + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.1" } }, "web3-eth-accounts": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.7.0.tgz", - "integrity": "sha512-Zwm7TlQXdXGRuS6+ib1YsR5fQwpfnFyL6UAZg1zERdrUrs3IkCZSL3yCP/8ZYbAjdTEwWljoott2iSqXNH09ug==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.1.tgz", + "integrity": "sha512-mgzxSYgN54/NsOFBO1Fq1KkXp1S5KlBvI/DlgvajU72rupoFMq6Cu6Yp9GUaZ/w2ij9PzEJuFJk174XwtfMCmg==", "requires": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.2", + "@ethereumjs/common": "2.5.0", + "@ethereumjs/tx": "3.3.2", "crypto-browserify": "3.12.0", "eth-lib": "0.2.8", "ethereumjs-util": "^7.0.10", "scrypt-js": "^3.0.1", - "uuid": "3.3.2", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-utils": "1.7.0" + "uuid": "^9.0.0", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" }, "dependencies": { "bn.js": { @@ -31835,86 +29106,89 @@ } }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" } } }, "web3-eth-contract": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.7.0.tgz", - "integrity": "sha512-2LY1Xwxu5rx468nqHuhvupQAIpytxIUj3mGL9uexszkhrQf05THVe3i4OnUCzkeN6B2cDztNOqLT3j9SSnVQDg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.1.tgz", + "integrity": "sha512-1wphnl+/xwCE2io44JKnN+ti3oa47BKRiVzvWd42icwRbcpFfRxH9QH+aQX3u8VZIISNH7dAkTWpGIIJgGFTmg==", "requires": { - "@types/bn.js": "^4.11.5", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-promievent": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-eth-abi": "1.7.0", - "web3-utils": "1.7.0" + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-utils": "1.8.1" + }, + "dependencies": { + "@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "requires": { + "@types/node": "*" + } + } } }, "web3-eth-ens": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.7.0.tgz", - "integrity": "sha512-I1bikYJJWQ/FJZIAvwsGOvzAgcRIkosWG4s1L6veRoXaU8OEJFeh4s00KcfHDxg7GWZZGbUSbdbzKpwRbWnvkg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.1.tgz", + "integrity": "sha512-FT8xTI9uN8RxeBQa/W8pLa2aoFh4+EE34w7W2271LICKzla1dtLyb6XSdn48vsUcPmhWsTVk9mO9RTU0l4LGQQ==", "requires": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-promievent": "1.7.0", - "web3-eth-abi": "1.7.0", - "web3-eth-contract": "1.7.0", - "web3-utils": "1.7.0" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-eth-iban": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.7.0.tgz", - "integrity": "sha512-1PFE/Og+sPZaug+M9TqVUtjOtq0HecE+SjDcsOOysXSzslNC2CItBGkcRwbvUcS+LbIkA7MFsuqYxOL0IV/gyA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.1.tgz", + "integrity": "sha512-DomoQBfvIdtM08RyMGkMVBOH0vpOIxSSQ+jukWk/EkMLGMWJtXw/K2c2uHAeq3L/VPWNB7zXV2DUEGV/lNE2Dg==", "requires": { - "bn.js": "^4.11.9", - "web3-utils": "1.7.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } + "bn.js": "^5.2.1", + "web3-utils": "1.8.1" } }, "web3-eth-personal": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.7.0.tgz", - "integrity": "sha512-Dr9RZTNOR80PhrPKGdktDUXpOgExEcCcosBj080lKCJFU1paSPj9Zfnth3u6BtIOXyKsVFTrpqekqUDyAwXnNw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.1.tgz", + "integrity": "sha512-myIYMvj7SDIoV9vE5BkVdon3pya1WinaXItugoii2VoTcQNPOtBxmYVH+XS5ErzCJlnxzphpQrkywyY64bbbCA==", "requires": { "@types/node": "^12.12.6", - "web3-core": "1.7.0", - "web3-core-helpers": "1.7.0", - "web3-core-method": "1.7.0", - "web3-net": "1.7.0", - "web3-utils": "1.7.0" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" }, "dependencies": { "@types/node": { - "version": "12.20.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.42.tgz", - "integrity": "sha512-aI3/oo5DzyiI5R/xAhxxRzfZlWlsbbqdgxfTPkqu/Zt+23GXiJvMCyPJT4+xKSXOnLqoL8jJYMLTwvK2M3a5hw==" + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" } } }, "web3-net": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.7.0.tgz", - "integrity": "sha512-8pmfU1Se7DmG40Pu8nOCKlhuI12VsVzCtdFDnLAai0zGVAOUuuOCK71B2aKm6u9amWBJjtOlyrCwvsG+QEd6dw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.1.tgz", + "integrity": "sha512-LyEJAwogdFo0UAXZqoSJGFjopdt+kLw0P00FSZn2yszbgcoI7EwC+nXiOsEe12xz4LqpYLOtbR7+gxgiTVjjHQ==", "requires": { - "web3-core": "1.7.0", - "web3-core-method": "1.7.0", - "web3-utils": "1.7.0" + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-provider-engine": { @@ -31998,69 +29272,74 @@ } }, "web3-providers-http": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.7.0.tgz", - "integrity": "sha512-Y9reeEiApfvQKLUUtrU4Z0c+H6b7BMWcsxjgoXndI1C5NB297mIUfltXxfXsh5C/jk5qn4Q3sJp3SwQTyVjH7Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.1.tgz", + "integrity": "sha512-1Zyts4O9W/UNEPkp+jyL19Jc3D15S4yp8xuLTjVhcUEAlHo24NDWEKxtZGUuHk4HrKL2gp8OlsDbJ7MM+ESDgg==", "requires": { - "web3-core-helpers": "1.7.0", - "xhr2-cookies": "1.1.0" + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.1" + }, + "dependencies": { + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "requires": { + "node-fetch": "2.6.7" + } + } } }, "web3-providers-ipc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.7.0.tgz", - "integrity": "sha512-U5YLXgu6fvAK4nnMYqo9eoml3WywgTym0dgCdVX/n1UegLIQ4nctTubBAuWQEJzmAzwh+a6ValGcE7ZApTRI7Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.1.tgz", + "integrity": "sha512-nw/W5nclvi+P2z2dYkLWReKLnocStflWqFl+qjtv0xn3MrUTyXMzSF0+61i77+16xFsTgzo4wS/NWIOVkR0EFA==", "requires": { "oboe": "2.1.5", - "web3-core-helpers": "1.7.0" + "web3-core-helpers": "1.8.1" } }, "web3-providers-ws": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.7.0.tgz", - "integrity": "sha512-0a8+lVV3JBf+eYnGOsdzOpftK1kis5X7s35QAdoaG5SDapnEylXFlR4xDSSSU88ZwMwvse8hvng2xW6A7oeWxw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.1.tgz", + "integrity": "sha512-TNefIDAMpdx57+YdWpYZ/xdofS0P+FfKaDYXhn24ie/tH9G+AB+UBSOKnjN0KSadcRSCMBwGPRiEmNHPavZdsA==", "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.0", + "web3-core-helpers": "1.8.1", "websocket": "^1.0.32" } }, "web3-shh": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.7.0.tgz", - "integrity": "sha512-RZhxcevALIPK178VZCpwMBvQeW+IoWtRJ4EMdegpbnETeZaC3aRUcs6vKnrf0jXJjm4J/E2Dt438Y1Ord/1IMw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.1.tgz", + "integrity": "sha512-sqHgarnfcY2Qt3PYS4R6YveHrDy7hmL09yeLLHHCI+RKirmjLVqV0rc5LJWUtlbYI+kDoa5gbgde489M9ZAC0g==", "requires": { - "web3-core": "1.7.0", - "web3-core-method": "1.7.0", - "web3-core-subscriptions": "1.7.0", - "web3-net": "1.7.0" + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-net": "1.8.1" } }, "web3-utils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.0.tgz", - "integrity": "sha512-O8Tl4Ky40Sp6pe89Olk2FsaUkgHyb5QAXuaKo38ms3CxZZ4d3rPGfjP9DNKGm5+IUgAZBNpF1VmlSmNCqfDI1w==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.1.tgz", + "integrity": "sha512-LgnM9p6V7rHHUGfpMZod+NST8cRfGzJ1BTXAyNo7A9cJX9LczBfSRxJp+U/GInYe9mby40t3v22AJdlELibnsQ==", "requires": { - "bn.js": "^4.11.9", + "bn.js": "^5.2.1", "ethereum-bloom-filters": "^1.0.6", "ethereumjs-util": "^7.1.0", "ethjs-unit": "0.1.6", "number-to-bn": "1.7.0", "randombytes": "^2.1.0", "utf8": "3.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } } }, "web3modal": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/web3modal/-/web3modal-1.9.5.tgz", - "integrity": "sha512-L5ME6zgoaCDa+T66skW9WpxGOJX6vU9v+7aLacoQJhU3AMTk784ionpX+Pg4UdhdM+UQW+odge32GkwEX11czQ==", + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/web3modal/-/web3modal-1.9.10.tgz", + "integrity": "sha512-gRByp+toRiADwkJLLGRXsnIVbLS1aJB71sJyryS6C7cF6jJ3cRN1LbPYEMObMyJkyjOZonx0CNZVAYGiD099aA==", "requires": { "detect-browser": "^5.1.0", "prop-types": "^15.7.2", @@ -32071,15 +29350,15 @@ } }, "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true }, "webpack": { - "version": "5.69.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz", - "integrity": "sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A==", + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", @@ -32087,33 +29366,27 @@ "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { - "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true - }, "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", @@ -32141,23 +29414,32 @@ } }, "webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true + } } }, "webpack-merge": { @@ -32194,17 +29476,28 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", "dev": true, "requires": { - "iconv-lite": "0.4.24" + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, "whatwg-fetch": { @@ -32213,20 +29506,19 @@ "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true }, "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "dev": true, "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" } }, "which": { @@ -32253,7 +29545,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" }, "which-typed-array": { "version": "1.1.7", @@ -32268,17 +29560,6 @@ "is-typed-array": "^1.1.7" } }, - "wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -32334,21 +29615,19 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "requires": { "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^3.0.7" } }, "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", "dev": true, "requires": {} }, @@ -32388,15 +29667,15 @@ "xhr2-cookies": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", - "integrity": "sha1-fXdEnQmZGX8VXLc7I99yUF7YnUg=", + "integrity": "sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==", "requires": { "cookiejar": "^2.1.1" } }, "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true }, "xmlchars": { @@ -32406,9 +29685,9 @@ "dev": true }, "xss": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.10.tgz", - "integrity": "sha512-qmoqrRksmzqSKvgqzN0055UFWY7OKx1/9JWeRswwEVX9fCG5jcYRxa/A2DHcmZX6VJvjzHRQ2STeeVcQkrmLSw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", + "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", "requires": { "commander": "^2.20.3", "cssfilter": "0.0.10" @@ -32434,7 +29713,7 @@ "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" }, "yallist": { "version": "4.0.0", @@ -32449,18 +29728,18 @@ "dev": true }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.0.0" }, "dependencies": { "y18n": { @@ -32468,15 +29747,15 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "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 - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 2933cb789e35..114b617fb6df 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -5,7 +5,7 @@ }, "private": true, "name": "blockscout", - "author": "POA Network", + "author": "Blockscout", "license": "GPL-3.0", "engines": { "node": "16.x", @@ -19,23 +19,24 @@ "eslint": "eslint js/**" }, "dependencies": { - "@fortawesome/fontawesome-free": "^6.0.0-beta3", - "@tarekraafat/autocomplete.js": "^10.2.6", - "@walletconnect/web3-provider": "^1.0.8", + "@fortawesome/fontawesome-free": "^6.2.1", + "@tarekraafat/autocomplete.js": "^10.2.7", + "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.0.0", - "bignumber.js": "^9.0.2", + "bignumber.js": "^9.1.1", "bootstrap": "^4.6.0", - "chart.js": "^3.7.0", - "chartjs-adapter-luxon": "^1.1.0", - "clipboard": "^2.0.9", - "core-js": "^3.20.3", + "chart.js": "^4.0.1", + "chartjs-adapter-luxon": "^1.3.0", + "clipboard": "^2.0.11", + "core-js": "^3.26.1", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", - "eth-net-props": "^1.0.33", - "highlight.js": "^11.4.0", + "eth-net-props": "^1.0.41", + "highlight.js": "^11.7.0", "https-browserify": "^1.0.0", "humps": "^2.0.1", - "jquery": "^3.4.0", + "jquery": "^3.6.2", + "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", "lodash.differenceby": "^4.8.0", "lodash.find": "^4.6.0", @@ -54,51 +55,54 @@ "lodash.omit": "^4.5.0", "lodash.rangeright": "^4.2.0", "lodash.reduce": "^4.6.0", - "luxon": "^2.4.0", - "moment": "^2.29.2", + "luxon": "^3.1.1", + "moment": "^2.29.4", "nanomorph": "^5.4.0", "numeral": "^2.0.6", "os-browserify": "^0.3.0", - "path-parser": "^4.2.0", + "path-parser": "^6.1.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", + "photoswipe": "^5.3.4", "pikaday": "^1.8.2", "popper.js": "^1.14.7", - "reduce-reducers": "^0.4.3", - "redux": "^4.0.5", + "reduce-reducers": "^1.0.4", + "redux": "^4.2.0", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.3.10", + "sweetalert2": "^11.6.15", "urijs": "^1.19.11", "url": "^0.11.0", - "util": "^0.12.3", - "web3": "^1.7.0", - "web3modal": "^1.9.5", - "xss": "^1.0.10" + "util": "^0.12.5", + "viewerjs": "^1.11.1", + "web3": "^1.8.1", + "web3modal": "^1.9.10", + "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.16.12", - "@babel/preset-env": "^7.16.11", - "autoprefixer": "^10.4.2", - "babel-loader": "^8.2.3", - "copy-webpack-plugin": "^10.2.1", + "@babel/core": "^7.20.5", + "@babel/preset-env": "^7.20.2", + "autoprefixer": "^10.4.13", + "babel-loader": "^9.1.0", + "copy-webpack-plugin": "^11.0.0", "css-loader": "^5.2.7", - "css-minimizer-webpack-plugin": "^3.4.1", - "eslint": "^8.17.0", + "css-minimizer-webpack-plugin": "^4.2.2", + "eslint": "^8.29.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-promise": "^6.1.1", "file-loader": "^6.2.0", - "jest": "^27.4.7", - "mini-css-extract-plugin": "^2.5.3", - "postcss": "^8.4.6", - "postcss-loader": "^6.2.1", - "sass": "^1.49.8", - "sass-loader": "^12.6.0", + "jest": "^29.3.1", + "jest-environment-jsdom": "^29.3.1", + "mini-css-extract-plugin": "^2.7.2", + "postcss": "^8.4.20", + "postcss-loader": "^7.0.2", + "sass": "^1.56.2", + "sass-loader": "^13.2.0", "style-loader": "^3.3.1", - "webpack": "^5.69.1", - "webpack-cli": "^4.9.2" + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" }, "jest": { "moduleNameMapper": { diff --git a/apps/block_scout_web/assets/static/503.html b/apps/block_scout_web/assets/static/503.html new file mode 100644 index 000000000000..f9ccfb681e7a --- /dev/null +++ b/apps/block_scout_web/assets/static/503.html @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + BlockScout Explorer + + + +
+
+
+
+
+ + + +
+
+

Service unavailable

+

Hang onâ€Ļ we’re working on a fix! Please come back soon to try again.

+ Back to home +
+
+
+
+
+ + + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/altlayer_logo.png b/apps/block_scout_web/assets/static/images/altlayer_logo.png index 6040921d2296..d42d3c0872b9 100644 Binary files a/apps/block_scout_web/assets/static/images/altlayer_logo.png and b/apps/block_scout_web/assets/static/images/altlayer_logo.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/internal-server-error.svg b/apps/block_scout_web/assets/static/images/errors-img/internal-server-error.svg new file mode 100644 index 000000000000..15b5c7911252 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/errors-img/internal-server-error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/assets/static/images/errors-img/page-not-found.svg b/apps/block_scout_web/assets/static/images/errors-img/page-not-found.svg new file mode 100644 index 000000000000..77b8bbe8ea25 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/errors-img/page-not-found.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/assets/static/images/errors-img/tx-not-found.svg b/apps/block_scout_web/assets/static/images/errors-img/tx-not-found.svg new file mode 100644 index 000000000000..1e6cfb918531 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/errors-img/tx-not-found.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/errors-img/unprocessable-entity.svg b/apps/block_scout_web/assets/static/images/errors-img/unprocessable-entity.svg new file mode 100644 index 000000000000..c4c8ff11c88b --- /dev/null +++ b/apps/block_scout_web/assets/static/images/errors-img/unprocessable-entity.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js index 84eefdfe6ced..d4f59631be08 100644 --- a/apps/block_scout_web/assets/webpack.config.js +++ b/apps/block_scout_web/assets/webpack.config.js @@ -48,6 +48,7 @@ const appJs = 'address-logs': './js/pages/address/logs.js', 'address-validations': './js/pages/address/validations.js', 'validated-transactions': './js/pages/transactions.js', + 'verified-contracts': './js/pages/verified_contracts.js', 'pending-transactions': './js/pages/pending_transactions.js', 'transaction': './js/pages/transaction.js', 'verification-form': './js/pages/verification_form.js', @@ -56,6 +57,7 @@ const appJs = 'admin-tasks': './js/pages/admin/tasks.js', 'token-contract': './js/pages/token_contract.js', 'smart-contract-helpers': './js/lib/smart_contract/index.js', + 'sol2uml': './js/pages/sol2uml.js', 'token-transfers-toggle': './js/lib/token_transfers_toggle.js', 'try-api': './js/lib/try_api.js', 'try-eth-api': './js/lib/try_eth_api.js', @@ -69,8 +71,10 @@ const appJs = 'search-results': './js/pages/search-results/search.js', 'token-overview': './js/pages/token/overview.js', 'export-csv': './css/export-csv.scss', - 'datepicker': './js/lib/datepicker.js', - 'dropzone': './js/lib/dropzone.js' + 'csv-download': './js/lib/csv_download.js', + 'dropzone': './js/lib/dropzone.js', + 'delete-item-handler': './js/pages/account/delete_item_handler.js', + 'public-tags-request-form': './js/lib/public_tags_request_form.js' }, output: { filename: '[name].js', @@ -81,6 +85,10 @@ const appJs = }, module: { rules: [ + { + test: /\.css$/, + use: ["style-loader", "css-loader"], + }, { test: /\.js$/, exclude: /node_modules/, @@ -156,11 +164,7 @@ const appJs = new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), new webpack.DefinePlugin({ 'process.env.SOCKET_ROOT': JSON.stringify(process.env.SOCKET_ROOT), - 'process.env.NETWORK_PATH': JSON.stringify(process.env.NETWORK_PATH), - 'process.env.CHAIN_ID': JSON.stringify(process.env.CHAIN_ID), - 'process.env.JSON_RPC': JSON.stringify(process.env.JSON_RPC), - 'process.env.SUBNETWORK': JSON.stringify(process.env.SUBNETWORK), - 'process.env.COIN_NAME': JSON.stringify(process.env.COIN_NAME) + 'process.env.NETWORK_PATH': JSON.stringify(process.env.NETWORK_PATH) }), new webpack.ProvidePlugin({ process: 'process/browser', diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index 104e9c8b76c9..815116d2752c 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -5,93 +5,42 @@ # is restricted to this project. import Config +network_path = + "NETWORK_PATH" + |> System.get_env("/") + |> (&(if String.ends_with?(&1, "/") do + String.trim_trailing(&1, "/") + else + &1 + end)).() + +api_path = + "API_PATH" + |> System.get_env("/") + |> (&(if String.ends_with?(&1, "/") do + String.trim_trailing(&1, "/") + else + &1 + end)).() + # General application configuration config :block_scout_web, namespace: BlockScoutWeb, - ecto_repos: [Explorer.Repo], - version: System.get_env("BLOCKSCOUT_VERSION"), - release_link: System.get_env("RELEASE_LINK"), - decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN"), - show_percentage: if(System.get_env("SHOW_ADDRESS_MARKETCAP_PERCENTAGE", "true") == "false", do: false, else: true), - checksum_address_hashes: if(System.get_env("CHECKSUM_ADDRESS_HASHES", "true") == "false", do: false, else: true) - -config :block_scout_web, BlockScoutWeb.Chain, - network: System.get_env("NETWORK"), - subnetwork: System.get_env("SUBNETWORK"), - network_icon: System.get_env("NETWORK_ICON"), - logo: System.get_env("LOGO"), - logo_footer: System.get_env("LOGO_FOOTER"), - logo_text: System.get_env("LOGO_TEXT"), - has_emission_funds: false, - show_maintenance_alert: System.get_env("SHOW_MAINTENANCE_ALERT", "false") == "true" + ecto_repos: [Explorer.Repo, Explorer.Repo.Account], + cookie_domain: System.get_env("SESSION_COOKIE_DOMAIN") config :block_scout_web, - link_to_other_explorers: System.get_env("LINK_TO_OTHER_EXPLORERS") == "true", - other_explorers: System.get_env("OTHER_EXPLORERS"), - other_networks: System.get_env("SUPPORTED_CHAINS"), - webapp_url: System.get_env("WEBAPP_URL"), - api_url: System.get_env("API_URL"), - apps_menu: if(System.get_env("APPS_MENU", "false") == "true", do: true, else: false), - external_apps: System.get_env("EXTERNAL_APPS"), - gas_price: System.get_env("GAS_PRICE", nil), - restricted_list: System.get_env("RESTRICTED_LIST", nil), - restricted_list_key: System.get_env("RESTRICTED_LIST_KEY", nil), - dark_forest_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST"), - 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_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), admin_panel_enabled: System.get_env("ADMIN_PANEL_ENABLED", "") == "true" -default_api_rate_limit = 50 -default_api_rate_limit_str = Integer.to_string(default_api_rate_limit) - -global_api_rate_limit_value = - "API_RATE_LIMIT" - |> System.get_env(default_api_rate_limit_str) - |> Integer.parse() - |> case do - {integer, ""} -> integer - _ -> default_api_rate_limit - end - -api_rate_limit_by_key_value = - "API_RATE_LIMIT_BY_KEY" - |> System.get_env(default_api_rate_limit_str) - |> Integer.parse() - |> case do - {integer, ""} -> integer - _ -> default_api_rate_limit - end - -api_rate_limit_by_ip_value = - "API_RATE_LIMIT_BY_IP" - |> System.get_env(default_api_rate_limit_str) - |> Integer.parse() - |> case do - {integer, ""} -> integer - _ -> default_api_rate_limit - end - -config :block_scout_web, :api_rate_limit, - global_limit: global_api_rate_limit_value, - limit_by_key: api_rate_limit_by_key_value, - limit_by_ip: api_rate_limit_by_ip_value, - static_api_key: System.get_env("API_RATE_LIMIT_STATIC_API_KEY", nil), - whitelisted_ips: System.get_env("API_RATE_LIMIT_WHITELISTED_IPS", nil) - config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true +config :block_scout_web, BlockScoutWeb.Counters.InternalTransactionsIndexedCounter, enabled: true + # Configures the endpoint config :block_scout_web, BlockScoutWeb.Endpoint, url: [ - scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "http", - host: System.get_env("BLOCKSCOUT_HOST") || "localhost", - path: System.get_env("NETWORK_PATH") || "/", - api_path: System.get_env("API_PATH") || "/" + path: network_path, + api_path: api_path ], render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)], pubsub_server: BlockScoutWeb.PubSub @@ -110,32 +59,10 @@ config :block_scout_web, BlockScoutWeb.SocialMedia, facebook: "PoaNetwork", instagram: "PoaNetwork" -# Configures History -price_chart_config = - if System.get_env("SHOW_PRICE_CHART", "false") != "false" do - %{market: [:price, :market_cap]} - else - %{} - end - -tx_chart_config = - if System.get_env("SHOW_TXS_CHART", "true") == "true" do - %{transactions: [:transactions_per_day]} - else - %{} - end - -config :block_scout_web, - chart_config: Map.merge(price_chart_config, tx_chart_config) - config :block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, # days history_size: 30 -config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, - # days - coin_balance_history_days: System.get_env("COIN_BALANCE_HISTORY_DAYS", "10") - config :ex_cldr, default_locale: "en", default_backend: BlockScoutWeb.Cldr @@ -172,6 +99,15 @@ config :block_scout_web, BlockScoutWeb.ApiRouter, config :block_scout_web, BlockScoutWeb.WebRouter, enabled: System.get_env("DISABLE_WEBAPP") != "true" +# Configures Ueberauth local settings +config :ueberauth, Ueberauth, + providers: [ + auth0: { + Ueberauth.Strategy.Auth0, + [callback_path: "/auth/auth0/callback", callback_params: ["path"]] + } + ] + config :hammer, backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]} diff --git a/apps/block_scout_web/config/dev.exs b/apps/block_scout_web/config/dev.exs index 02b038790cfd..d26e1b28d26c 100644 --- a/apps/block_scout_web/config/dev.exs +++ b/apps/block_scout_web/config/dev.exs @@ -7,31 +7,7 @@ import Config # watchers to your application. For example, we use it # with webpack to recompile .js and .css sources. -port = - case System.get_env("PORT") && Integer.parse(System.get_env("PORT")) do - {port, _} -> port - :error -> nil - nil -> nil - end - config :block_scout_web, BlockScoutWeb.Endpoint, - secret_key_base: - System.get_env("SECRET_KEY_BASE") || "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5", - http: [ - port: port || 4000 - ], - url: [ - scheme: "http", - host: System.get_env("BLOCKSCOUT_HOST") || "localhost", - path: System.get_env("NETWORK_PATH") || "/", - api_path: System.get_env("API_PATH") || "/" - ], - https: [ - port: (port && port + 1) || 4001, - cipher_suite: :strong, - certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem", - keyfile: System.get_env("KEYFILE") || "priv/cert/selfsigned_key.pem" - ], debug_errors: true, code_reloader: true, check_origin: false, @@ -86,3 +62,5 @@ config :logger, :api, # Set a higher stacktrace during development. Avoid configuring such # in production as building large stacktraces may be expensive. config :phoenix, :stacktrace_depth, 20 + +config :block_scout_web, :captcha_helper, BlockScoutWeb.CaptchaHelper diff --git a/apps/block_scout_web/config/prod.exs b/apps/block_scout_web/config/prod.exs index fd433c47f110..366b8864afd1 100644 --- a/apps/block_scout_web/config/prod.exs +++ b/apps/block_scout_web/config/prod.exs @@ -15,17 +15,7 @@ import Config # which you typically run after static files are built. config :block_scout_web, BlockScoutWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json", - force_ssl: false, - secret_key_base: System.get_env("SECRET_KEY_BASE"), - check_origin: System.get_env("CHECK_ORIGIN", "false") == "true" || false, - http: [port: System.get_env("PORT")], - url: [ - scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "https", - port: System.get_env("PORT"), - host: System.get_env("BLOCKSCOUT_HOST") || "localhost", - path: System.get_env("NETWORK_PATH") || "/", - api_path: System.get_env("API_PATH") || "/" - ] + force_ssl: false config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true @@ -39,3 +29,5 @@ config :logger, :api, path: Path.absname("logs/prod/api.log"), metadata_filter: [fetcher: :api], rotate: %{max_bytes: 52_428_800, keep: 19} + +config :block_scout_web, :captcha_helper, BlockScoutWeb.CaptchaHelper diff --git a/apps/block_scout_web/config/runtime/test.exs b/apps/block_scout_web/config/runtime/test.exs new file mode 100644 index 000000000000..f465b5106282 --- /dev/null +++ b/apps/block_scout_web/config/runtime/test.exs @@ -0,0 +1,21 @@ +import Config + +alias EthereumJSONRPC.Variant + +config :explorer, Explorer.ExchangeRates, enabled: false, store: :none + +config :explorer, Explorer.KnownTokens, enabled: false, store: :none + +config :ueberauth, Ueberauth.Strategy.Auth0.OAuth, + domain: "example.com", + client_id: "clien_id", + client_secret: "secrets" + +config :ueberauth, Ueberauth, + logout_url: "example.com/logout", + logout_return_to_url: "example.com/return" + +variant = Variant.get() + +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../explorer/config/test") +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../indexer/config/test") diff --git a/apps/block_scout_web/config/test.exs b/apps/block_scout_web/config/test.exs index a2188c180c7f..9c3414fa4f48 100644 --- a/apps/block_scout_web/config/test.exs +++ b/apps/block_scout_web/config/test.exs @@ -20,8 +20,16 @@ config :logger, :block_scout_web, # Configure wallaby config :wallaby, screenshot_on_failure: true, driver: Wallaby.Chrome, js_errors: false -config :explorer, Explorer.ExchangeRates, enabled: false, store: :none +config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: false -config :explorer, Explorer.KnownTokens, enabled: false, store: :none +config :block_scout_web, BlockScoutWeb.Counters.InternalTransactionsIndexedCounter, enabled: false -config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: false +config :block_scout_web, :captcha_helper, BlockScoutWeb.TestCaptchaHelper + +config :ueberauth, Ueberauth, + providers: [ + auth0: { + Ueberauth.Strategy.Auth0, + [callback_url: "example.com/callback"] + } + ] diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 6063941f69b9..99ced87556f9 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -13,30 +13,167 @@ defmodule BlockScoutWeb.ApiRouter do Router for API """ use BlockScoutWeb, :router + alias BlockScoutWeb.Plug.{CheckAccountAPI, CheckApiV2} pipeline :api do plug(:accepts, ["json"]) end + pipeline :account_api do + plug(:fetch_session) + plug(:protect_from_forgery) + plug(CheckAccountAPI) + end + + pipeline :api_v2 do + plug(CheckApiV2) + plug(:fetch_session) + plug(:protect_from_forgery) + end + + alias BlockScoutWeb.Account.Api.V1.{TagsController, UserController} + + scope "/account/v1", as: :account_v1 do + pipe_through(:api) + pipe_through(:account_api) + + get("/get_csrf", UserController, :get_csrf) + + scope "/user" do + get("/info", UserController, :info) + + get("/watchlist", UserController, :watchlist) + delete("/watchlist/:id", UserController, :delete_watchlist) + post("/watchlist", UserController, :create_watchlist) + put("/watchlist/:id", UserController, :update_watchlist) + + get("/api_keys", UserController, :api_keys) + delete("/api_keys/:api_key", UserController, :delete_api_key) + post("/api_keys", UserController, :create_api_key) + put("/api_keys/:api_key", UserController, :update_api_key) + + get("/custom_abis", UserController, :custom_abis) + delete("/custom_abis/:id", UserController, :delete_custom_abi) + post("/custom_abis", UserController, :create_custom_abi) + put("/custom_abis/:id", UserController, :update_custom_abi) + + get("/public_tags", UserController, :public_tags_requests) + delete("/public_tags/:id", UserController, :delete_public_tags_request) + post("/public_tags", UserController, :create_public_tags_request) + put("/public_tags/:id", UserController, :update_public_tags_request) + + scope "/tags" do + get("/address/", UserController, :tags_address) + get("/address/:id", UserController, :tags_address) + delete("/address/:id", UserController, :delete_tag_address) + post("/address/", UserController, :create_tag_address) + put("/address/:id", UserController, :update_tag_address) + + get("/transaction/", UserController, :tags_transaction) + get("/transaction/:id", UserController, :tags_transaction) + delete("/transaction/:id", UserController, :delete_tag_transaction) + post("/transaction/", UserController, :create_tag_transaction) + put("/transaction/:id", UserController, :update_tag_transaction) + end + end + end + + scope "/account/v1" do + pipe_through(:api) + pipe_through(:account_api) + + scope "/tags" do + get("/address/:address_hash", TagsController, :tags_address) + + get("/transaction/:transaction_hash", TagsController, :tags_transaction) + end + end + + scope "/v2", as: :api_v2 do + pipe_through(:api) + pipe_through(:api_v2) + + alias BlockScoutWeb.API.V2 + + get("/search", V2.SearchController, :search) + + scope "/config" do + get("/json-rpc-url", V2.ConfigController, :json_rpc_url) + end + + scope "/transactions" do + get("/", V2.TransactionController, :transactions) + get("/:transaction_hash", V2.TransactionController, :transaction) + get("/:transaction_hash/token-transfers", V2.TransactionController, :token_transfers) + get("/:transaction_hash/internal-transactions", V2.TransactionController, :internal_transactions) + get("/:transaction_hash/logs", V2.TransactionController, :logs) + get("/:transaction_hash/raw-trace", V2.TransactionController, :raw_trace) + end + + scope "/blocks" do + get("/", V2.BlockController, :blocks) + get("/:block_hash_or_number", V2.BlockController, :block) + get("/:block_hash_or_number/transactions", V2.BlockController, :transactions) + end + + scope "/addresses" do + get("/:address_hash", V2.AddressController, :address) + get("/:address_hash/counters", V2.AddressController, :counters) + get("/:address_hash/token-balances", V2.AddressController, :token_balances) + get("/:address_hash/transactions", V2.AddressController, :transactions) + get("/:address_hash/token-transfers", V2.AddressController, :token_transfers) + get("/:address_hash/internal-transactions", V2.AddressController, :internal_transactions) + get("/:address_hash/logs", V2.AddressController, :logs) + get("/:address_hash/blocks-validated", V2.AddressController, :blocks_validated) + get("/:address_hash/coin-balance-history", V2.AddressController, :coin_balance_history) + get("/:address_hash/coin-balance-history-by-day", V2.AddressController, :coin_balance_history_by_day) + end + + scope "/tokens" do + get("/:address_hash", V2.TokenController, :token) + get("/:address_hash/counters", V2.TokenController, :counters) + get("/:address_hash/transfers", V2.TokenController, :transfers) + get("/:address_hash/holders", V2.TokenController, :holders) + end + + scope "/main-page" do + get("/blocks", V2.MainPageController, :blocks) + get("/transactions", V2.MainPageController, :transactions) + get("/indexing-status", V2.MainPageController, :indexing_status) + end + + scope "/stats" do + get("/", V2.StatsController, :stats) + + scope "/charts" do + get("/transactions", V2.StatsController, :transactions_chart) + get("/market", V2.StatsController, :market_chart) + end + end + end + scope "/v1", as: :api_v1 do pipe_through(:api) alias BlockScoutWeb.API.{EthRPC, RPC, V1} alias BlockScoutWeb.API.V1.HealthController + alias BlockScoutWeb.API.V2.SearchController + # leave the same endpoint in v1 in order to keep backward compatibility + get("/search", SearchController, :search) get("/health", HealthController, :health) get("/gas-price-oracle", V1.GasPriceOracleController, :gas_price_oracle) - if Application.get_env(:block_scout_web, __MODULE__)[:reading_enabled] do + if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do get("/supply", V1.SupplyController, :supply) post("/eth-rpc", EthRPC.EthController, :eth_request) end - if Application.get_env(:block_scout_web, __MODULE__)[:writing_enabled] do + if Application.compile_env(:block_scout_web, __MODULE__)[:writing_enabled] do post("/decompiled_smart_contract", V1.DecompiledSmartContractController, :create) post("/verified_smart_contracts", V1.VerifiedSmartContractController, :create) end - if Application.get_env(:block_scout_web, __MODULE__)[:reading_enabled] do + if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do forward("/", RPC.RPCTranslator, %{ "block" => {RPC.BlockController, []}, "account" => {RPC.AddressController, []}, @@ -54,7 +191,7 @@ defmodule BlockScoutWeb.ApiRouter do pipe_through(:api) alias BlockScoutWeb.API.{EthRPC, RPC} - if Application.get_env(:block_scout_web, __MODULE__)[:reading_enabled] do + if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do post("/eth-rpc", EthRPC.EthController, :eth_request) forward("/", RPCTranslatorForwarder, %{ diff --git a/apps/block_scout_web/lib/block_scout_web/application.ex b/apps/block_scout_web/lib/block_scout_web/application.ex index 07ff0f87f1c1..2d84cc8b101b 100644 --- a/apps/block_scout_web/lib/block_scout_web/application.ex +++ b/apps/block_scout_web/lib/block_scout_web/application.ex @@ -5,7 +5,8 @@ defmodule BlockScoutWeb.Application do use Application - alias BlockScoutWeb.Counters.BlocksIndexedCounter + alias BlockScoutWeb.API.APILogger + alias BlockScoutWeb.Counters.{BlocksIndexedCounter, InternalTransactionsIndexedCounter} alias BlockScoutWeb.{Endpoint, Prometheus} alias BlockScoutWeb.RealtimeEventHandler @@ -15,6 +16,18 @@ defmodule BlockScoutWeb.Application do Prometheus.Instrumenter.setup() Prometheus.Exporter.setup() + APILogger.message( + "Current global API rate limit #{inspect(Application.get_env(:block_scout_web, :api_rate_limit)[:global_limit])} reqs/sec" + ) + + APILogger.message( + "Current API rate limit by key #{inspect(Application.get_env(:block_scout_web, :api_rate_limit)[:limit_by_key])} reqs/sec" + ) + + APILogger.message( + "Current API rate limit by IP #{inspect(Application.get_env(:block_scout_web, :api_rate_limit)[:limit_by_ip])} reqs/sec" + ) + # Define workers and child supervisors to be supervised children = [ # Start the endpoint when the application starts @@ -22,7 +35,8 @@ defmodule BlockScoutWeb.Application do child_spec(Endpoint, []), {Absinthe.Subscription, Endpoint}, {RealtimeEventHandler, name: RealtimeEventHandler}, - {BlocksIndexedCounter, name: BlocksIndexedCounter} + {BlocksIndexedCounter, name: BlocksIndexedCounter}, + {InternalTransactionsIndexedCounter, name: InternalTransactionsIndexedCounter} ] opts = [strategy: :one_for_one, name: BlockScoutWeb.Supervisor, max_restarts: 1_000] diff --git a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex new file mode 100644 index 000000000000..abeb3d16ebf6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex @@ -0,0 +1,27 @@ +defmodule BlockScoutWeb.CaptchaHelper do + @moduledoc """ + A helper for CAPTCHA + """ + + @callback recaptcha_passed?(String.t() | nil) :: bool + @spec recaptcha_passed?(String.t() | nil) :: bool + def recaptcha_passed?(nil), do: false + + def recaptcha_passed?(recaptcha_response) do + re_captcha_secret_key = Application.get_env(:block_scout_web, :re_captcha_secret_key) + body = "secret=#{re_captcha_secret_key}&response=#{recaptcha_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: 200, body: body}} -> + case Jason.decode!(body) do + %{"success" => true} -> true + _ -> false + end + + _ -> + false + end + end +end 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 927e5f254656..3d710fe8686d 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -24,6 +24,7 @@ defmodule BlockScoutWeb.Chain do Block, InternalTransaction, Log, + SmartContract, Token, TokenTransfer, Transaction, @@ -216,6 +217,10 @@ defmodule BlockScoutWeb.Chain do [paging_options: %{@default_paging_options | key: {name, type, value}}] end + def paging_options(%{"smart_contract_id" => id}) do + [paging_options: %{@default_paging_options | key: {id}}] + 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 @@ -232,6 +237,11 @@ defmodule BlockScoutWeb.Chain do end end + def fetch_page_number(%{"items_count" => item_count_str}) do + {items_count, _} = Integer.parse(item_count_str) + div(items_count, @page_size) + 1 + end + def fetch_page_number(_), do: 1 def update_page_parameters(new_page_number, new_page_size, %PagingOptions{} = options) do @@ -349,6 +359,10 @@ defmodule BlockScoutWeb.Chain do %{"block_number" => block_number} end + defp paging_params(%SmartContract{} = smart_contract) do + %{"smart_contract_id" => smart_contract.id} + end + defp paging_params(%{ address_hash: address_hash, tx_hash: tx_hash, 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 00eb13bf5f66..e73a54dff91a 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 @@ -4,6 +4,8 @@ defmodule BlockScoutWeb.AddressChannel do """ use BlockScoutWeb, :channel + alias BlockScoutWeb.API.V2.AddressView, as: AddressViewAPI + alias BlockScoutWeb.{ AddressCoinBalanceView, AddressView, @@ -56,6 +58,20 @@ defmodule BlockScoutWeb.AddressChannel do end end + def handle_out( + "balance_update", + %{address: address, exchange_rate: exchange_rate}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "balance", %{ + balance: address.fetched_coin_balance.value, + block_number: address.fetched_coin_balance_block_number, + exchange_rate: exchange_rate.usd_value + }) + + {:noreply, socket} + end + def handle_out( "balance_update", %{address: address, exchange_rate: exchange_rate}, @@ -88,6 +104,12 @@ defmodule BlockScoutWeb.AddressChannel do end end + def handle_out("count", %{count: count}, %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket) do + push(socket, "count", %{count: to_string(count)}) + + {:noreply, socket} + end + def handle_out("count", %{count: count}, socket) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) @@ -96,6 +118,16 @@ defmodule BlockScoutWeb.AddressChannel do {:noreply, socket} end + def handle_out( + "internal_transaction", + %{address: _address, internal_transaction: _internal_transaction}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "internal_transaction", %{internal_transaction: 1}) + + {:noreply, socket} + end + def handle_out("internal_transaction", %{address: address, internal_transaction: internal_transaction}, socket) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) @@ -120,6 +152,22 @@ defmodule BlockScoutWeb.AddressChannel do def handle_out("token_transfer", data, socket), do: handle_token_transfer(data, socket, "token_transfer") + def handle_out( + "coin_balance", + %{block_number: block_number}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + coin_balance = Chain.get_coin_balance(socket.assigns.address_hash, block_number) + + rendered_coin_balance = AddressViewAPI.render("coin_balance.json", %{coin_balance: coin_balance}) + + push(socket, "coin_balance", %{coin_balance: rendered_coin_balance}) + + push_current_coin_balance(socket, block_number, coin_balance) + + {:noreply, socket} + end + def handle_out("coin_balance", %{block_number: block_number}, socket) do coin_balance = Chain.get_coin_balance(socket.assigns.address_hash, block_number) @@ -142,8 +190,23 @@ defmodule BlockScoutWeb.AddressChannel do {:noreply, socket} end + def handle_out("pending_transaction", data, %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket), + do: handle_transaction(data, socket, "pending_transaction") + def handle_out("pending_transaction", data, socket), do: handle_transaction(data, socket, "transaction") + def push_current_coin_balance( + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket, + block_number, + coin_balance + ) do + push(socket, "current_coin_balance", %{ + coin_balance: (coin_balance && coin_balance.value) || %Wei{value: Decimal.new(0)}, + exchange_rate: (Market.get_exchange_rate(Explorer.coin()) || Token.null()).usd_value, + block_number: block_number + }) + end + def push_current_coin_balance(socket, block_number, coin_balance) do {:ok, hash} = Chain.string_to_address_hash(socket.assigns.address_hash) @@ -172,6 +235,16 @@ defmodule BlockScoutWeb.AddressChannel do }) end + def handle_transaction( + %{address: _address, transaction: _transaction}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket, + event + ) do + push(socket, event, %{transaction: 1}) + + {:noreply, socket} + end + def handle_transaction(%{address: address, transaction: transaction}, socket, event) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) @@ -195,13 +268,29 @@ defmodule BlockScoutWeb.AddressChannel do {:noreply, socket} end + def handle_token_transfer( + %{address: _address, token_transfer: _token_transfer}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket, + event + ) do + push(socket, event, %{token_transfer: 1}) + + {:noreply, socket} + end + def handle_token_transfer(%{address: address, token_transfer: token_transfer}, socket, event) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) transaction = Transaction |> Repo.get_by(hash: token_transfer.transaction_hash) - |> Repo.preload([:from_address, :to_address, :block, token_transfers: [:from_address, :to_address, :token]]) + |> Repo.preload([ + :from_address, + :to_address, + :block, + :created_contract_address, + token_transfers: [:from_address, :to_address, :token] + ]) rendered = View.render_to_string( diff --git a/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex index cea7e4410420..560a1d4d96c2 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex @@ -4,8 +4,10 @@ defmodule BlockScoutWeb.BlockChannel do """ use BlockScoutWeb, :channel + alias BlockScoutWeb.API.V2.BlockView, as: BlockViewAPI alias BlockScoutWeb.{BlockView, ChainView} alias Phoenix.View + alias Timex.Duration intercept(["new_block"]) @@ -17,6 +19,21 @@ defmodule BlockScoutWeb.BlockChannel do {:ok, %{}, socket} end + def handle_out( + "new_block", + %{block: block, average_block_time: average_block_time}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + rendered_block = BlockViewAPI.render("block.json", %{block: block, socket: nil}) + + push(socket, "new_block", %{ + average_block_time: to_string(Duration.to_milliseconds(average_block_time)), + block: rendered_block + }) + + {:noreply, socket} + end + def handle_out("new_block", %{block: block, average_block_time: average_block_time}, socket) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) diff --git a/apps/block_scout_web/lib/block_scout_web/channels/exchange_rate_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/exchange_rate_channel.ex index 13569b83999e..ef5d5208e32a 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/exchange_rate_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/exchange_rate_channel.ex @@ -10,6 +10,20 @@ defmodule BlockScoutWeb.ExchangeRateChannel do {:ok, %{}, socket} end + def handle_out( + "new_rate", + %{exchange_rate: exchange_rate, market_history_data: market_history_data}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "new_rate", %{ + exchange_rate: exchange_rate.usd_value, + available_supply: exchange_rate.available_supply, + chart_data: market_history_data + }) + + {:noreply, socket} + end + def handle_out("new_rate", %{exchange_rate: exchange_rate, market_history_data: market_history_data}, socket) do push(socket, "new_rate", %{ exchange_rate: exchange_rate, diff --git a/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex index 6f6ff3a70bc3..e53ac616e7de 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex @@ -17,6 +17,16 @@ defmodule BlockScoutWeb.RewardChannel do end end + def handle_out( + "new_reward", + %{emission_funds: _emission_funds, validator: _validator}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "new_reward", %{reward: 1}) + + {:noreply, socket} + end + def handle_out("new_reward", %{emission_funds: emission_funds, validator: validator}, socket) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) diff --git a/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex index 2d3ef58d3b12..56b22c06f3d7 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex @@ -14,12 +14,18 @@ defmodule BlockScoutWeb.TokenChannel do {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") @burn_address_hash burn_address_hash - def join("tokens:new_token_transfer", _params, socket) do + def join("tokens:" <> _transaction_hash, _params, socket) do {:ok, %{}, socket} end - def join("tokens:" <> _transaction_hash, _params, socket) do - {:ok, %{}, socket} + def handle_out( + "token_transfer", + %{token_transfer: _token_transfer}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "token_transfer", %{token_transfer: 1}) + + {:noreply, socket} end def handle_out("token_transfer", %{token_transfer: token_transfer}, socket) do diff --git a/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex index ac1085ea29c1..669991e04159 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex @@ -30,6 +30,16 @@ defmodule BlockScoutWeb.TransactionChannel do {:ok, %{}, socket} end + def handle_out( + "pending_transaction", + %{transaction: _transaction}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "pending_transaction", %{pending_transaction: 1}) + + {:noreply, socket} + end + def handle_out("pending_transaction", %{transaction: transaction}, socket) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) @@ -50,6 +60,16 @@ defmodule BlockScoutWeb.TransactionChannel do {:noreply, socket} end + def handle_out( + "transaction", + %{transaction: _transaction}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "transaction", %{transaction: 1}) + + {:noreply, socket} + end + def handle_out("transaction", %{transaction: transaction}, socket) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) diff --git a/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex new file mode 100644 index 000000000000..b012b8db2a9f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex @@ -0,0 +1,19 @@ +defmodule BlockScoutWeb.UserSocketV2 do + @moduledoc """ + Module to distinct new and old UI websocket connections + """ + use Phoenix.Socket + + channel("addresses:*", BlockScoutWeb.AddressChannel) + channel("blocks:*", BlockScoutWeb.BlockChannel) + channel("exchange_rate:*", BlockScoutWeb.ExchangeRateChannel) + channel("rewards:*", BlockScoutWeb.RewardChannel) + channel("transactions:*", BlockScoutWeb.TransactionChannel) + channel("tokens:*", BlockScoutWeb.TokenChannel) + + def connect(_params, socket) do + {:ok, socket} + end + + def id(_socket), do: nil +end diff --git a/apps/block_scout_web/lib/block_scout_web/controller.ex b/apps/block_scout_web/lib/block_scout_web/controller.ex index abdc89986592..7776342ea9e0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controller.ex @@ -49,6 +49,13 @@ defmodule BlockScoutWeb.Controller do if path =~ network_path do path else + network_path = + if String.starts_with?(path, "/") do + String.trim_trailing(network_path, "/") + else + network_path + end + network_path <> path end else diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex new file mode 100644 index 000000000000..56c3e4422916 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex @@ -0,0 +1,83 @@ +defmodule BlockScoutWeb.Account.Api.V1.FallbackController do + use Phoenix.Controller + + alias BlockScoutWeb.Account.Api.V1.UserView + alias Ecto.Changeset + + def call(conn, {:identity, _}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "User not found"}) + end + + def call(conn, {:watchlist, _}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "Watchlist not found"}) + end + + def call(conn, {:error, %{reason: :item_not_found}}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "Item not found"}) + end + + def call(conn, {:error, %Changeset{} = changeset}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(UserView) + |> render(:changeset_errors, changeset: changeset) + end + + def call(conn, {:create_tag, {:error, message}}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(UserView) + |> render(:message, %{message: message}) + end + + def call(conn, {:watchlist_delete, false}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "Watchlist address not found"}) + end + + def call(conn, {:tag_delete, false}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "Tag not found"}) + end + + def call(conn, {:api_key_delete, false}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "Api key not found"}) + end + + def call(conn, {:custom_abi_delete, false}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "Custom ABI not found"}) + end + + def call(conn, {:public_tag_delete, false}) do + conn + |> put_status(:not_found) + |> put_view(UserView) + |> render(:message, %{message: "Error"}) + end + + def call(conn, {:auth, _}) do + conn + |> put_status(:unauthorized) + |> put_view(UserView) + |> render(:message, %{message: "Unauthorized"}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex new file mode 100644 index 000000000000..91f8ab58333c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex @@ -0,0 +1,87 @@ +defmodule BlockScoutWeb.Account.Api.V1.TagsController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + + alias BlockScoutWeb.Models.{GetAddressTags, GetTransactionTags, UserFromAuth} + alias Explorer.Account.Identity + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Hash.{Address, Full} + + action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController) + + def tags_address(conn, %{"address_hash" => address_hash}) do + personal_tags = + if is_nil(current_user(conn)) do + %{personal_tags: [], watchlist_names: []} + else + uid = current_user(conn).id + + with {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:watchlist, %{watchlists: [watchlist | _]}} <- + {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, + {:address_hash, {:ok, address_hash}} <- {:address_hash, Address.cast(address_hash)} do + GetAddressTags.get_address_tags(address_hash, %{id: identity.id, watchlist_id: watchlist.id}) + else + _ -> + %{personal_tags: [], watchlist_names: []} + end + end + + public_tags = + case Address.cast(address_hash) do + {:ok, address_hash} -> + GetAddressTags.get_public_tags(address_hash) + + _ -> + %{common_tags: []} + end + + conn + |> put_status(200) + |> render(:address_tags, %{tags_map: Map.merge(personal_tags, public_tags)}) + end + + def tags_transaction(conn, %{"transaction_hash" => transaction_hash}) do + transaction = + with {:ok, transaction_hash} <- Full.cast(transaction_hash), + {:ok, transaction} <- Chain.hash_to_transaction(transaction_hash) do + transaction + else + _ -> + nil + end + + personal_tags = + if is_nil(current_user(conn)) do + %{personal_tags: [], watchlist_names: [], personal_tx_tag: nil} + else + uid = current_user(conn).id + + with {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:watchlist, %{watchlists: [watchlist | _]}} <- + {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, + false <- is_nil(transaction) do + GetTransactionTags.get_transaction_with_addresses_tags(transaction, %{ + id: identity.id, + watchlist_id: watchlist.id + }) + else + _ -> + %{personal_tags: [], watchlist_names: [], personal_tx_tag: nil} + end + end + + public_tags_from = + if is_nil(transaction), do: [], else: GetAddressTags.get_public_tags(transaction.from_address_hash).common_tags + + public_tags_to = + if is_nil(transaction), do: [], else: GetAddressTags.get_public_tags(transaction.to_address_hash).common_tags + + public_tags = %{common_tags: public_tags_from ++ public_tags_to} + + conn + |> put_status(200) + |> render(:transaction_tags, %{tags_map: Map.merge(personal_tags, public_tags)}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex new file mode 100644 index 000000000000..837d64771dc8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex @@ -0,0 +1,473 @@ +defmodule BlockScoutWeb.Account.Api.V1.UserController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import Ecto.Query, only: [from: 2] + + alias BlockScoutWeb.Models.UserFromAuth + alias Explorer.Account.Api.Key, as: ApiKey + alias Explorer.Account.CustomABI + alias Explorer.Account.{Identity, PublicTagsRequest, TagAddress, TagTransaction, WatchlistAddress} + alias Explorer.ExchangeRates.Token + alias Explorer.{Market, Repo} + alias Plug.CSRFProtection + + action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController) + + @ok_message "OK" + + def info(conn, _params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)} do + conn + |> put_status(200) + |> render(:user_info, %{identity: identity}) + end + end + + def watchlist(conn, _params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:watchlist, %{watchlists: [watchlist | _]}} <- + {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, + watchlist_with_addresses <- preload_watchlist_addresses(watchlist) do + conn + |> put_status(200) + |> render(:watchlist_addresses, %{ + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + watchlist_addresses: watchlist_with_addresses.watchlist_addresses + }) + end + end + + def delete_watchlist(conn, %{"id" => watchlist_address_id}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:watchlist, %{watchlists: [watchlist | _]}} <- + {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, + {count, _} <- WatchlistAddress.delete(watchlist_address_id, watchlist.id), + {:watchlist_delete, true} <- {:watchlist_delete, count > 0} do + conn + |> put_status(200) + |> render(:message, %{message: @ok_message}) + end + end + + def create_watchlist(conn, %{ + "address_hash" => address_hash, + "name" => name, + "notification_settings" => %{ + "native" => %{ + "incoming" => watch_coin_input, + "outcoming" => watch_coin_output + }, + "ERC-20" => %{ + "incoming" => watch_erc_20_input, + "outcoming" => watch_erc_20_output + }, + "ERC-721" => %{ + "incoming" => watch_erc_721_input, + "outcoming" => watch_erc_721_output + } + # , + # "ERC-1155" => %{ + # "incoming" => watch_erc_1155_input, + # "outcoming" => watch_erc_1155_output + # } + }, + "notification_methods" => %{ + "email" => notify_email + } + }) do + watchlist_params = %{ + name: name, + watch_coin_input: watch_coin_input, + watch_coin_output: watch_coin_output, + watch_erc_20_input: watch_erc_20_input, + watch_erc_20_output: watch_erc_20_output, + watch_erc_721_input: watch_erc_721_input, + watch_erc_721_output: watch_erc_721_output, + watch_erc_1155_input: watch_erc_721_input, + watch_erc_1155_output: watch_erc_721_output, + notify_email: notify_email, + address_hash: address_hash + } + + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:watchlist, %{watchlists: [watchlist | _]}} <- + {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, + {:ok, watchlist_address} <- + WatchlistAddress.create(Map.put(watchlist_params, :watchlist_id, watchlist.id)) do + conn + |> put_status(200) + |> render(:watchlist_address, %{ + watchlist_address: watchlist_address, + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + }) + end + end + + def update_watchlist(conn, %{ + "id" => watchlist_address_id, + "address_hash" => address_hash, + "name" => name, + "notification_settings" => %{ + "native" => %{ + "incoming" => watch_coin_input, + "outcoming" => watch_coin_output + }, + "ERC-20" => %{ + "incoming" => watch_erc_20_input, + "outcoming" => watch_erc_20_output + }, + "ERC-721" => %{ + "incoming" => watch_erc_721_input, + "outcoming" => watch_erc_721_output + } + # , + # "ERC-1155" => %{ + # "incoming" => watch_erc_1155_input, + # "outcoming" => watch_erc_1155_output + # } + }, + "notification_methods" => %{ + "email" => notify_email + } + }) do + watchlist_params = %{ + id: watchlist_address_id, + name: name, + watch_coin_input: watch_coin_input, + watch_coin_output: watch_coin_output, + watch_erc_20_input: watch_erc_20_input, + watch_erc_20_output: watch_erc_20_output, + watch_erc_721_input: watch_erc_721_input, + watch_erc_721_output: watch_erc_721_output, + watch_erc_1155_input: watch_erc_721_input, + watch_erc_1155_output: watch_erc_721_output, + notify_email: notify_email, + address_hash: address_hash + } + + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:watchlist, %{watchlists: [watchlist | _]}} <- + {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, + {:ok, watchlist_address} <- + WatchlistAddress.update(Map.put(watchlist_params, :watchlist_id, watchlist.id)) do + conn + |> put_status(200) + |> render(:watchlist_address, %{ + watchlist_address: watchlist_address, + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + }) + end + end + + def tags_address(conn, _params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + address_tags <- TagAddress.get_tags_address_by_identity_id(identity.id) do + conn + |> put_status(200) + |> render(:address_tags, %{address_tags: address_tags}) + end + end + + def delete_tag_address(conn, %{"id" => tag_id}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {count, _} <- TagAddress.delete(tag_id, identity.id), + {:tag_delete, true} <- {:tag_delete, count > 0} do + conn + |> put_status(200) + |> render(:message, %{message: @ok_message}) + end + end + + def create_tag_address(conn, %{"address_hash" => address_hash, "name" => name}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, address_tag} <- + TagAddress.create(%{ + name: name, + address_hash: address_hash, + identity_id: identity.id + }) do + conn + |> put_status(200) + |> render(:address_tag, %{address_tag: address_tag}) + end + end + + def update_tag_address(conn, %{"id" => tag_id} = attrs) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, address_tag} <- + TagAddress.update( + reject_nil_map_values(%{ + id: tag_id, + name: attrs["name"], + address_hash: attrs["address_hash"], + identity_id: identity.id + }) + ) do + conn + |> put_status(200) + |> render(:address_tag, %{address_tag: address_tag}) + end + end + + def tags_transaction(conn, _params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + transaction_tags <- TagTransaction.get_tags_transaction_by_identity_id(identity.id) do + conn + |> put_status(200) + |> render(:transaction_tags, %{transaction_tags: transaction_tags}) + end + end + + def delete_tag_transaction(conn, %{"id" => tag_id}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {count, _} <- TagTransaction.delete(tag_id, identity.id), + {:tag_delete, true} <- {:tag_delete, count > 0} do + conn + |> put_status(200) + |> render(:message, %{message: @ok_message}) + end + end + + def create_tag_transaction(conn, %{"transaction_hash" => tx_hash, "name" => name}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, transaction_tag} <- + TagTransaction.create(%{ + name: name, + tx_hash: tx_hash, + identity_id: identity.id + }) do + conn + |> put_status(200) + |> render(:transaction_tag, %{transaction_tag: transaction_tag}) + end + end + + def update_tag_transaction(conn, %{"id" => tag_id} = attrs) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, transaction_tag} <- + TagTransaction.update( + reject_nil_map_values(%{ + id: tag_id, + name: attrs["name"], + tx_hash: attrs["transaction_hash"], + identity_id: identity.id + }) + ) do + conn + |> put_status(200) + |> render(:transaction_tag, %{transaction_tag: transaction_tag}) + end + end + + def api_keys(conn, _params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + api_keys <- ApiKey.get_api_keys_by_identity_id(identity.id) do + conn + |> put_status(200) + |> render(:api_keys, %{api_keys: api_keys}) + end + end + + def delete_api_key(conn, %{"api_key" => api_key_uuid}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {count, _} <- ApiKey.delete(api_key_uuid, identity.id), + {:api_key_delete, true} <- {:api_key_delete, count > 0} do + conn + |> put_status(200) + |> render(:message, %{message: @ok_message}) + end + end + + def create_api_key(conn, %{"name" => api_key_name}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, api_key} <- + ApiKey.create(%{name: api_key_name, identity_id: identity.id}) do + conn + |> put_status(200) + |> render(:api_key, %{api_key: api_key}) + end + end + + def update_api_key(conn, %{"name" => api_key_name, "api_key" => api_key_value}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, api_key} <- + ApiKey.update(%{value: api_key_value, name: api_key_name, identity_id: identity.id}) do + conn + |> put_status(200) + |> render(:api_key, %{api_key: api_key}) + end + end + + def custom_abis(conn, _params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + custom_abis <- CustomABI.get_custom_abis_by_identity_id(identity.id) do + conn + |> put_status(200) + |> render(:custom_abis, %{custom_abis: custom_abis}) + end + end + + def delete_custom_abi(conn, %{"id" => id}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {count, _} <- CustomABI.delete(id, identity.id), + {:custom_abi_delete, true} <- {:custom_abi_delete, count > 0} do + conn + |> put_status(200) + |> render(:message, %{message: @ok_message}) + end + end + + def create_custom_abi(conn, %{"contract_address_hash" => contract_address_hash, "name" => name, "abi" => abi}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, custom_abi} <- + CustomABI.create(%{ + name: name, + address_hash: contract_address_hash, + abi: abi, + identity_id: identity.id + }) do + conn + |> put_status(200) + |> render(:custom_abi, %{custom_abi: custom_abi}) + end + end + + def update_custom_abi( + conn, + %{ + "id" => id + } = params + ) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, custom_abi} <- + CustomABI.update( + reject_nil_map_values(%{ + id: id, + name: params["name"], + address_hash: params["contract_address_hash"], + abi: params["abi"], + identity_id: identity.id + }) + ) do + conn + |> put_status(200) + |> render(:custom_abi, %{custom_abi: custom_abi}) + end + end + + def public_tags_requests(conn, _params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + public_tags_requests <- PublicTagsRequest.get_public_tags_requests_by_identity_id(identity.id) do + conn + |> put_status(200) + |> render(:public_tags_requests, %{public_tags_requests: public_tags_requests}) + end + end + + def delete_public_tags_request(conn, %{"id" => id, "remove_reason" => remove_reason}) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:public_tag_delete, true} <- + {:public_tag_delete, + PublicTagsRequest.mark_as_deleted_public_tags_request(%{ + id: id, + identity_id: identity.id, + remove_reason: remove_reason + })} do + conn + |> put_status(200) + |> render(:message, %{message: @ok_message}) + end + end + + def create_public_tags_request(conn, params) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, public_tags_request} <- + PublicTagsRequest.create(%{ + full_name: params["full_name"], + email: params["email"], + tags: params["tags"], + website: params["website"], + additional_comment: params["additional_comment"], + addresses: params["addresses"], + company: params["company"], + is_owner: params["is_owner"], + identity_id: identity.id + }) do + conn + |> put_status(200) + |> render(:public_tags_request, %{public_tags_request: public_tags_request}) + end + end + + def update_public_tags_request( + conn, + %{ + "id" => id + } = params + ) do + with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, + {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)}, + {:ok, public_tags_request} <- + PublicTagsRequest.update( + reject_nil_map_values(%{ + id: id, + full_name: params["full_name"], + email: params["email"], + tags: params["tags"], + website: params["website"], + additional_comment: params["additional_comment"], + addresses: params["addresses"], + company: params["company"], + is_owner: params["is_owner"], + identity_id: identity.id + }) + ) do + conn + |> put_status(200) + |> render(:public_tags_request, %{public_tags_request: public_tags_request}) + end + end + + def get_csrf(conn, _) do + with {:auth, %{id: _}} <- {:auth, current_user(conn)} do + conn + |> put_resp_header("x-bs-account-csrf", CSRFProtection.get_csrf_token()) + |> put_status(200) + |> render(:message, %{message: "ok"}) + end + end + + defp reject_nil_map_values(map) when is_map(map) do + Map.reject(map, fn {_k, v} -> is_nil(v) end) + end + + defp preload_watchlist_addresses(watchlist) do + watchlist + |> Repo.account_repo().preload(watchlist_addresses: from(wa in WatchlistAddress, order_by: [desc: wa.id])) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api_key_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api_key_controller.ex new file mode 100644 index 000000000000..55c0b05283b6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api_key_controller.ex @@ -0,0 +1,65 @@ +defmodule BlockScoutWeb.Account.ApiKeyController do + use BlockScoutWeb, :controller + + alias Explorer.Account.Api.Key, as: ApiKey + + import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] + + def new(conn, _params) do + authenticate!(conn) + + render(conn, "form.html", method: :create, api_key: empty_api_key()) + end + + def create(conn, %{"key" => api_key}) do + current_user = authenticate!(conn) + + case ApiKey.create(%{name: api_key["name"], identity_id: current_user.id}) do + {:ok, _} -> + redirect(conn, to: api_key_path(conn, :index)) + + {:error, invalid_api_key} -> + render(conn, "form.html", method: :create, api_key: invalid_api_key) + end + end + + def create(conn, _) do + redirect(conn, to: api_key_path(conn, :index)) + end + + def index(conn, _params) do + current_user = authenticate!(conn) + + render(conn, "index.html", api_keys: ApiKey.get_api_keys_by_identity_id(current_user.id)) + end + + def edit(conn, %{"id" => api_key}) do + current_user = authenticate!(conn) + + case ApiKey.get_api_key_by_value_and_identity_id(api_key, current_user.id) do + nil -> + not_found(conn) + + %ApiKey{} = api_key -> + render(conn, "form.html", method: :update, api_key: ApiKey.changeset(api_key)) + end + end + + def update(conn, %{"id" => api_key, "key" => %{"value" => api_key, "name" => name}}) do + current_user = authenticate!(conn) + + ApiKey.update(%{value: api_key, identity_id: current_user.id, name: name}) + + redirect(conn, to: api_key_path(conn, :index)) + end + + def delete(conn, %{"id" => api_key}) do + current_user = authenticate!(conn) + + ApiKey.delete(api_key, current_user.id) + + redirect(conn, to: api_key_path(conn, :index)) + end + + defp empty_api_key, do: ApiKey.changeset() +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex new file mode 100644 index 000000000000..c97ab5fb3a70 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex @@ -0,0 +1,89 @@ +defmodule BlockScoutWeb.Account.AuthController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Models.UserFromAuth + alias Explorer.Account + alias Explorer.Repo.ConfigHelper + alias Plug.CSRFProtection + + plug(Ueberauth) + + def request(conn, _) do + not_found(conn) + end + + def logout(conn, _params) do + conn + |> configure_session(drop: true) + |> redirect(to: root()) + end + + def profile(conn, _params), + do: conn |> get_session(:current_user) |> do_profile(conn) + + defp do_profile(nil, conn), + do: redirect(conn, to: root()) + + defp do_profile(%{} = user, conn), + do: render(conn, :profile, user: user) + + def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do + conn + |> put_flash(:error, "Failed to authenticate.") + |> redirect(to: root()) + end + + def callback(%{assigns: %{ueberauth_auth: auth}} = conn, params) do + case UserFromAuth.find_or_create(auth) do + {:ok, user} -> + CSRFProtection.get_csrf_token() + + conn + |> put_session(:current_user, user) + |> redirect(to: redirect_path(params["path"])) + + {:error, reason} -> + conn + |> put_flash(:error, reason) + |> redirect(to: root()) + end + end + + def callback(conn, _) do + not_found(conn) + end + + # for importing in other controllers + def authenticate!(conn) do + current_user(conn) || redirect(conn, to: root()) + end + + def current_user(%{private: %{plug_session: %{"current_user" => _}}} = conn) do + if Account.enabled?() do + get_session(conn, :current_user) + else + nil + end + end + + def current_user(_), do: nil + + defp root do + ConfigHelper.network_path() + end + + defp redirect_path(path) when is_binary(path) do + case URI.parse(path) do + %URI{path: "/" <> path} -> + "/" <> path + + %URI{path: path} when is_binary(path) -> + "/" <> path + + _ -> + root() + end + end + + defp redirect_path(_), do: root() +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/custom_abi_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/custom_abi_controller.ex new file mode 100644 index 000000000000..dcd8fca985e4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/custom_abi_controller.ex @@ -0,0 +1,87 @@ +defmodule BlockScoutWeb.Account.CustomABIController do + use BlockScoutWeb, :controller + + alias Ecto.Changeset + alias Explorer.Account.CustomABI + + import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] + + def new(conn, _params) do + authenticate!(conn) + + render(conn, "form.html", method: :create, custom_abi: empty_custom_abi()) + end + + def create(conn, %{"custom_abi" => custom_abi}) do + current_user = authenticate!(conn) + + case CustomABI.create(%{ + name: custom_abi["name"], + address_hash: custom_abi["address_hash"], + abi: custom_abi["abi"], + identity_id: current_user.id + }) do + {:ok, _} -> + redirect(conn, to: custom_abi_path(conn, :index)) + + {:error, invalid_custom_abi} -> + render(conn, "form.html", method: :create, custom_abi: invalid_custom_abi) + end + end + + def create(conn, _) do + redirect(conn, to: custom_abi_path(conn, :index)) + end + + def index(conn, _params) do + current_user = authenticate!(conn) + + render(conn, "index.html", custom_abis: CustomABI.get_custom_abis_by_identity_id(current_user.id)) + end + + def edit(conn, %{"id" => id}) do + current_user = authenticate!(conn) + + case CustomABI.get_custom_abi_by_id_and_identity_id(id, current_user.id) do + nil -> + not_found(conn) + + %CustomABI{} = custom_abi -> + render(conn, "form.html", method: :update, custom_abi: CustomABI.changeset_without_constraints(custom_abi)) + end + end + + def update(conn, %{"id" => id, "custom_abi" => %{"abi" => abi, "name" => name, "address_hash" => address_hash}}) do + current_user = authenticate!(conn) + + case CustomABI.update(%{ + id: id, + name: name, + address_hash: address_hash, + abi: abi, + identity_id: current_user.id + }) do + {:error, %Changeset{} = custom_abi} -> + render(conn, "form.html", method: :update, custom_abi: custom_abi) + + _ -> + redirect(conn, to: custom_abi_path(conn, :index)) + end + end + + def update(conn, _) do + authenticate!(conn) + + redirect(conn, to: custom_abi_path(conn, :index)) + end + + def delete(conn, %{"id" => id}) do + current_user = authenticate!(conn) + + CustomABI.delete(id, current_user.id) + + redirect(conn, to: custom_abi_path(conn, :index)) + end + + defp empty_custom_abi, do: CustomABI.changeset_without_constraints() +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/public_tags_request_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/public_tags_request_controller.ex new file mode 100644 index 000000000000..a380a2cc3ca9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/public_tags_request_controller.ex @@ -0,0 +1,114 @@ +defmodule BlockScoutWeb.Account.PublicTagsRequestController do + use BlockScoutWeb, :controller + + alias Ecto.Changeset + alias Explorer.Account.PublicTagsRequest + + import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] + + def index(conn, _params) do + current_user = authenticate!(conn) + + render(conn, "index.html", + public_tags_requests: PublicTagsRequest.get_public_tags_requests_by_identity_id(current_user.id) + ) + end + + def new(conn, _params) do + current_user = authenticate!(conn) + + render(conn, "form.html", + method: :create, + public_tags_request: + PublicTagsRequest.changeset_without_constraints(%PublicTagsRequest{}, %{ + full_name: current_user.name, + email: current_user.email + }) + ) + end + + def create(conn, %{"public_tags_request" => public_tags_request}) do + current_user = authenticate!(conn) + + case PublicTagsRequest.create(%{ + full_name: public_tags_request["full_name"], + email: public_tags_request["email"], + tags: public_tags_request["tags"], + website: public_tags_request["website"], + additional_comment: public_tags_request["additional_comment"], + addresses: public_tags_request["addresses"], + company: public_tags_request["company"], + is_owner: public_tags_request["is_owner"], + identity_id: current_user.id + }) do + {:ok, _} -> + redirect(conn, to: public_tags_request_path(conn, :index)) + + {:error, invalid_public_tags_request} -> + render(conn, "form.html", method: :create, public_tags_request: invalid_public_tags_request) + end + end + + def create(conn, _) do + redirect(conn, to: public_tags_request_path(conn, :index)) + end + + def edit(conn, %{"id" => id}) do + current_user = authenticate!(conn) + + case PublicTagsRequest.get_public_tags_request_by_id_and_identity_id(id, current_user.id) do + nil -> + not_found(conn) + + %PublicTagsRequest{} = public_tags_request -> + render(conn, "form.html", + method: :update, + public_tags_request: PublicTagsRequest.changeset_without_constraints(public_tags_request) + ) + end + end + + def update(conn, %{ + "id" => id, + "public_tags_request" => public_tags_request + }) do + current_user = authenticate!(conn) + + case PublicTagsRequest.update(%{ + id: id, + full_name: public_tags_request["full_name"], + email: public_tags_request["email"], + tags: public_tags_request["tags"], + website: public_tags_request["website"], + additional_comment: public_tags_request["additional_comment"], + addresses: public_tags_request["addresses"], + company: public_tags_request["company"], + is_owner: public_tags_request["is_owner"], + identity_id: current_user.id + }) do + {:error, %Changeset{} = public_tags_request} -> + render(conn, "form.html", method: :update, public_tags_request: public_tags_request) + + _ -> + redirect(conn, to: public_tags_request_path(conn, :index)) + end + end + + def update(conn, _) do + authenticate!(conn) + + redirect(conn, to: public_tags_request_path(conn, :index)) + end + + def delete(conn, %{"id" => id, "remove_reason" => remove_reason}) do + current_user = authenticate!(conn) + + PublicTagsRequest.mark_as_deleted_public_tags_request(%{ + id: id, + identity_id: current_user.id, + remove_reason: remove_reason + }) + + redirect(conn, to: public_tags_request_path(conn, :index)) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_address_controller.ex new file mode 100644 index 000000000000..8ca30a959b69 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_address_controller.ex @@ -0,0 +1,49 @@ +defmodule BlockScoutWeb.Account.TagAddressController do + use BlockScoutWeb, :controller + + alias Explorer.Account.TagAddress + + import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] + + def index(conn, _params) do + current_user = authenticate!(conn) + + render(conn, "index.html", address_tags: TagAddress.get_tags_address_by_identity_id(current_user.id)) + end + + def new(conn, _params) do + authenticate!(conn) + + render(conn, "form.html", tag_address: new_tag()) + end + + def create(conn, %{"tag_address" => tag_address}) do + current_user = authenticate!(conn) + + case TagAddress.create(%{ + name: tag_address["name"], + address_hash: tag_address["address_hash"], + identity_id: current_user.id + }) do + {:ok, _} -> + redirect(conn, to: tag_address_path(conn, :index)) + + {:error, invalid_tag_address} -> + render(conn, "form.html", tag_address: invalid_tag_address) + end + end + + def create(conn, _) do + redirect(conn, to: tag_address_path(conn, :index)) + end + + def delete(conn, %{"id" => id}) do + current_user = authenticate!(conn) + + TagAddress.delete(id, current_user.id) + + redirect(conn, to: tag_address_path(conn, :index)) + end + + defp new_tag, do: TagAddress.changeset() +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex new file mode 100644 index 000000000000..cba3dea142ac --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex @@ -0,0 +1,49 @@ +defmodule BlockScoutWeb.Account.TagTransactionController do + use BlockScoutWeb, :controller + + alias Explorer.Account.TagTransaction + + import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] + + def index(conn, _params) do + current_user = authenticate!(conn) + + render(conn, "index.html", tx_tags: TagTransaction.get_tags_transaction_by_identity_id(current_user.id)) + end + + def new(conn, _params) do + authenticate!(conn) + + render(conn, "form.html", tag_transaction: new_tag()) + end + + def create(conn, %{"tag_transaction" => tag_address}) do + current_user = authenticate!(conn) + + case TagTransaction.create(%{ + name: tag_address["name"], + tx_hash: tag_address["tx_hash"], + identity_id: current_user.id + }) do + {:ok, _} -> + redirect(conn, to: tag_transaction_path(conn, :index)) + + {:error, invalid_tag_transaction} -> + render(conn, "form.html", tag_transaction: invalid_tag_transaction) + end + end + + def create(conn, _) do + redirect(conn, to: tag_transaction_path(conn, :index)) + end + + def delete(conn, %{"id" => id}) do + current_user = authenticate!(conn) + + TagTransaction.delete(id, current_user.id) + + redirect(conn, to: tag_transaction_path(conn, :index)) + end + + defp new_tag, do: TagTransaction.changeset() +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_address_controller.ex new file mode 100644 index 000000000000..55236895a427 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_address_controller.ex @@ -0,0 +1,92 @@ +defmodule BlockScoutWeb.Account.WatchlistAddressController do + use BlockScoutWeb, :controller + + alias Explorer.Account.WatchlistAddress + + import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] + + def new(conn, _params) do + authenticate!(conn) + + render(conn, "form.html", method: :create, watchlist_address: empty_watchlist_address()) + end + + def create(conn, %{"watchlist_address" => wa_params}) do + current_user = authenticate!(conn) + + case WatchlistAddress.create(params_to_attributes(wa_params, current_user.watchlist_id)) do + {:ok, _watchlist_address} -> + redirect(conn, to: watchlist_path(conn, :show)) + + {:error, changeset} -> + render(conn, "form.html", method: :create, watchlist_address: changeset) + end + end + + def edit(conn, %{"id" => id}) do + current_user = authenticate!(conn) + + case WatchlistAddress.get_watchlist_address_by_id_and_watchlist_id(id, current_user.watchlist_id) do + nil -> + not_found(conn) + + %WatchlistAddress{} = watchlist_address -> + render(conn, "form.html", method: :update, watchlist_address: WatchlistAddress.changeset(watchlist_address)) + end + end + + def update(conn, %{"id" => id, "watchlist_address" => wa_params}) do + current_user = authenticate!(conn) + + case wa_params + |> params_to_attributes(current_user.watchlist_id) + |> Map.put(:id, id) + |> WatchlistAddress.update() do + {:ok, _watchlist_address} -> + redirect(conn, to: watchlist_path(conn, :show)) + + {:error, changeset} -> + render(conn, "form.html", method: :update, watchlist_address: changeset) + end + end + + def delete(conn, %{"id" => id}) do + current_user = authenticate!(conn) + + WatchlistAddress.delete(id, current_user.watchlist_id) + + redirect(conn, to: watchlist_path(conn, :show)) + end + + defp empty_watchlist_address, do: WatchlistAddress.changeset() + + defp params_to_attributes( + %{ + "address_hash" => address_hash, + "name" => name, + "watch_coin_input" => watch_coin_input, + "watch_coin_output" => watch_coin_output, + "watch_erc_20_input" => watch_erc_20_input, + "watch_erc_20_output" => watch_erc_20_output, + "watch_erc_721_input" => watch_nft_input, + "watch_erc_721_output" => watch_nft_output, + "notify_email" => notify_email + }, + watchlist_id + ) do + %{ + address_hash: address_hash, + name: name, + watch_coin_input: watch_coin_input, + watch_coin_output: watch_coin_output, + watch_erc_20_input: watch_erc_20_input, + watch_erc_20_output: watch_erc_20_output, + watch_erc_721_input: watch_nft_input, + watch_erc_721_output: watch_nft_output, + watch_erc_1155_input: watch_nft_input, + watch_erc_1155_output: watch_nft_output, + notify_email: notify_email, + watchlist_id: watchlist_id + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_controller.ex new file mode 100644 index 000000000000..f8c548322f44 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_controller.ex @@ -0,0 +1,26 @@ +defmodule BlockScoutWeb.Account.WatchlistController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1] + import Ecto.Query, only: [from: 2] + + alias Explorer.Account.{Watchlist, WatchlistAddress} + alias Explorer.Repo + + def show(conn, _params) do + current_user = authenticate!(conn) + + render( + conn, + "show.html", + watchlist: watchlist_with_addresses(current_user) + ) + end + + defp watchlist_with_addresses(user) do + Watchlist + |> Repo.account_repo().get(user.watchlist_id) + |> Repo.account_repo().preload(watchlist_addresses: from(wa in WatchlistAddress, order_by: [desc: wa.id])) + |> WatchlistAddress.preload_address_fetched_coin_balance() + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex index b024c102a0bd..6f8f2202456f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex @@ -5,7 +5,9 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] alias BlockScoutWeb.{AccessHelpers, AddressCoinBalanceView, Controller} alias Explorer.{Chain, Market} @@ -76,7 +78,8 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), current_path: Controller.current_full_path(conn), - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> @@ -101,7 +104,8 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do coin_balance_status: nil, exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), - current_path: Controller.current_full_path(conn) + current_path: Controller.current_full_path(conn), + tags: get_address_tags(address_hash, current_user(conn)) ) _ -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex index a2f1d30cbb56..ffd65b260821 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex @@ -2,6 +2,9 @@ defmodule BlockScoutWeb.AddressContractController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.AccessHelpers alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController alias Explorer.{Chain, Market} @@ -29,7 +32,8 @@ defmodule BlockScoutWeb.AddressContractController do address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}) + counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> 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 c44a525341c8..7a56e647a483 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 @@ -45,6 +45,25 @@ defmodule BlockScoutWeb.AddressContractVerificationController do end end + def create( + conn, + %{ + "smart_contract" => smart_contract, + "external_libraries" => external_libraries, + "file" => files, + "verification_type" => "multi-part-files" + } + ) do + files_array = + files + |> Map.values() + |> read_files() + + Que.add(SolidityPublisherWorker, {"multipart", smart_contract, files_array, external_libraries, conn}) + + send_resp(conn, 204, "") + end + def create( conn, %{ @@ -52,7 +71,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "external_libraries" => external_libraries } ) do - Que.add(SolidityPublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn}) + Que.add(SolidityPublisherWorker, {"flattened", smart_contract, external_libraries, conn}) send_resp(conn, 204, "") end @@ -70,7 +89,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do with %Plug.Upload{path: path} <- get_one_json(files_array), {:ok, json_input} <- File.read(path) do - Que.add(SolidityPublisherWorker, {smart_contract, json_input, conn}) + Que.add(SolidityPublisherWorker, {"json_web", smart_contract, json_input, conn}) else _ -> nil @@ -226,6 +245,14 @@ defmodule BlockScoutWeb.AddressContractVerificationController do |> Enum.at(0) end + # sobelow_skip ["Traversal.FileModule"] + defp read_files(plug_uploads) do + Enum.reduce(plug_uploads, %{}, fn %Plug.Upload{path: path, filename: file_name}, acc -> + {:ok, file_content} = File.read(path) + Map.put(acc, file_name, file_content) + end) + end + defp prepare_verification_error(msg, address_hash_string, conn) do [ {:contract_verification_result, 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 2da0424b8f62..632c05781452 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 @@ -46,7 +46,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do "external_libraries" => external_libraries } ) do - Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn}) + Que.add(PublisherWorker, {"flattened", smart_contract, external_libraries, conn}) send_resp(conn, 204, "") end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex new file mode 100644 index 000000000000..cdc6d44b32be --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex @@ -0,0 +1,41 @@ +defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias Explorer.Chain + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} + + def new(conn, %{"address_id" => address_hash_string}) do + if Chain.smart_contract_fully_verified?(address_hash_string) do + address_contract_path = + conn + |> address_contract_path(:index, address_hash_string) + |> Controller.full_path() + + redirect(conn, to: address_contract_path) + else + changeset = + SmartContract.changeset( + %SmartContract{address_hash: address_hash_string}, + %{} + ) + + compiler_versions = + case CompilerVersion.fetch_versions(:solc) do + {:ok, compiler_versions} -> + compiler_versions + + {:error, _} -> + [] + end + + render(conn, "new.html", + changeset: changeset, + address_hash: address_hash_string, + evm_versions: CodeCompiler.allowed_evm_versions(), + compiler_versions: compiler_versions + ) + 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 13cf9c20d238..982ac6128fa4 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 @@ -1,8 +1,12 @@ defmodule BlockScoutWeb.AddressController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{ AccessHelpers, AddressTransactionController, @@ -10,7 +14,6 @@ defmodule BlockScoutWeb.AddressController do Controller } - alias Explorer.Counters.{AddressTokenTransfersCounter, AddressTransactionsCounter, AddressTransactionsGasUsageCounter} alias Explorer.{Chain, Market} alias Explorer.Chain.Wei alias Explorer.ExchangeRates.Token @@ -101,7 +104,8 @@ defmodule BlockScoutWeb.AddressController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), - current_path: Controller.current_full_path(conn) + current_path: Controller.current_full_path(conn), + tags: get_address_tags(address_hash, current_user(conn)) ) else :error -> @@ -130,7 +134,8 @@ defmodule BlockScoutWeb.AddressController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), - current_path: Controller.current_full_path(conn) + current_path: Controller.current_full_path(conn), + tags: get_address_tags(address_hash, current_user(conn)) ) _ -> @@ -142,7 +147,7 @@ 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 - {validation_count} = address_counters(address) + {validation_count} = Chain.address_counters(address) transactions_from_db = address.transactions_count || 0 token_transfers_from_db = address.token_transfers_count || 0 @@ -164,57 +169,4 @@ defmodule BlockScoutWeb.AddressController do }) end end - - defp address_counters(address) do - validation_count_task = - Task.async(fn -> - validation_count(address) - end) - - Task.start_link(fn -> - transaction_count(address) - end) - - Task.start_link(fn -> - token_transfers_count(address) - end) - - Task.start_link(fn -> - gas_usage_count(address) - end) - - [ - validation_count_task - ] - |> Task.yield_many(:infinity) - |> Enum.map(fn {_task, res} -> - case res do - {:ok, result} -> - result - - {:exit, reason} -> - raise "Query fetching address counters terminated: #{inspect(reason)}" - - nil -> - raise "Query fetching address counters timed out." - end - end) - |> List.to_tuple() - end - - def transaction_count(address) 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 - - defp validation_count(address) do - Chain.address_to_validation_count(address.hash) - end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex index 079d5d9093ce..968326cbf2ce 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex @@ -1,6 +1,9 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.AccessHelpers alias Explorer.{Chain, Market} alias Explorer.ExchangeRates.Token @@ -16,7 +19,8 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}) + counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex index a72ba8e25649..bfc8ce84fb8a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex @@ -5,7 +5,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] alias BlockScoutWeb.{AccessHelpers, Controller, InternalTransactionView} alias Explorer.{Chain, Market} @@ -86,7 +88,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do current_path: Controller.current_full_path(conn), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), filter: params["filter"], - counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}) + counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> @@ -112,7 +115,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do coin_balance_status: nil, exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), - current_path: Controller.current_full_path(conn) + current_path: Controller.current_full_path(conn), + tags: get_address_tags(address_hash, current_user(conn)) ) _ -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex index bcb138ca08b9..742c58cc0885 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex @@ -3,8 +3,12 @@ defmodule BlockScoutWeb.AddressLogsController do Manages events logs tab. """ + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{AccessHelpers, AddressLogsView, Controller} alias Explorer.{Chain, Market} alias Explorer.ExchangeRates.Token @@ -64,7 +68,8 @@ defmodule BlockScoutWeb.AddressLogsController do current_path: Controller.current_full_path(conn), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}) + counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), + tags: get_address_tags(address_hash, current_user(conn)) ) else _ -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex index 4734b8f0f343..2751e69dd0d1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex @@ -8,10 +8,15 @@ defmodule BlockScoutWeb.AddressReadContractController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.AccessHelpers + alias BlockScoutWeb.AddressView alias Explorer.{Chain, Market} alias Explorer.Chain.Address alias Explorer.ExchangeRates.Token + alias Explorer.SmartContract.Reader alias Indexer.Fetcher.CoinBalanceOnDemand def index(conn, %{"address_id" => address_hash_string} = params) do @@ -25,23 +30,63 @@ defmodule BlockScoutWeb.AddressReadContractController do } ] + custom_abi = AddressView.fetch_custom_abi(conn, address_hash_string) + custom_abi? = AddressView.check_custom_abi_for_having_read_functions(custom_abi) + + need_wallet_custom_abi? = + !is_nil(custom_abi) && Reader.read_functions_required_wallet_from_abi(custom_abi.abi) != [] + + base_params = [ + type: :regular, + action: :read, + custom_abi: custom_abi?, + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + ] + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true), false <- is_nil(address.smart_contract), + need_wallet? <- Reader.read_functions_required_wallet_from_abi(address.smart_contract.abi) != [], {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do render( conn, "index.html", - address: address, - type: :regular, - action: :read, - coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + base_params ++ + [ + address: address, + non_custom_abi: true, + need_wallet: need_wallet? || need_wallet_custom_abi?, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) + ] ) else _ -> - not_found(conn) + if custom_abi? do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.find_contract_address(address_hash, address_options, false), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + base_params ++ + [ + address: address, + non_custom_abi: false, + need_wallet: need_wallet_custom_abi?, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) + ] + ) + else + _ -> + not_found(conn) + end + else + not_found(conn) + end end end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex index 7ed05ef4b42c..564655a0c1a8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex @@ -2,6 +2,9 @@ defmodule BlockScoutWeb.AddressReadProxyController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.AccessHelpers alias Explorer.{Chain, Market} alias Explorer.Chain.Address @@ -31,7 +34,8 @@ defmodule BlockScoutWeb.AddressReadProxyController do action: :read, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) 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 99b29f40827c..5470034f894d 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 @@ -2,6 +2,8 @@ defmodule BlockScoutWeb.AddressTokenController do use BlockScoutWeb, :controller import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1] + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] alias BlockScoutWeb.{AccessHelpers, AddressTokenView, Controller} alias Explorer.{Chain, Market} @@ -74,7 +76,8 @@ defmodule BlockScoutWeb.AddressTokenController do current_path: Controller.current_full_path(conn), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex index 3cfee0da7d4c..eeebc3236a5d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex @@ -1,6 +1,9 @@ defmodule BlockScoutWeb.AddressTokenTransferController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{AccessHelpers, Controller, TransactionView} alias Explorer.ExchangeRates.Token alias Explorer.{Chain, Market} @@ -109,7 +112,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), current_path: Controller.current_full_path(conn), token: token, - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> @@ -200,7 +204,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), filter: params["filter"], current_path: Controller.current_full_path(conn), - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> 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 fed2fe2da8ab..679b0f62e93d 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 @@ -5,8 +5,12 @@ defmodule BlockScoutWeb.AddressTransactionController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{AccessHelpers, Controller, TransactionView} alias Explorer.{Chain, Market} @@ -22,6 +26,8 @@ defmodule BlockScoutWeb.AddressTransactionController do alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View + alias Plug.Conn + @transaction_necessity_by_association [ necessity_by_association: %{ [created_contract_address: :names] => :optional, @@ -120,7 +126,8 @@ defmodule BlockScoutWeb.AddressTransactionController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), - current_path: Controller.current_full_path(conn) + current_path: Controller.current_full_path(conn), + tags: get_address_tags(address_hash, current_user(conn)) ) else :error -> @@ -149,7 +156,8 @@ defmodule BlockScoutWeb.AddressTransactionController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), - current_path: Controller.current_full_path(conn) + current_path: Controller.current_full_path(conn), + tags: get_address_tags(address_hash, current_user(conn)) ) _ -> @@ -158,104 +166,127 @@ defmodule BlockScoutWeb.AddressTransactionController do 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 + defp captcha_helper do + :block_scout_web + |> Application.get_env(:captcha_helper) + end + + defp put_resp_params(conn, file_name) do + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=#{file_name}") + |> put_resp_cookie("csv-downloaded", "true", max_age: 86_400, http_only: false) + |> send_chunked(200) + end + + defp items_csv( + conn, + %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => recaptcha_response + }, + csv_export_module, + file_name + ) + 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 + {:ok, address} <- Chain.hash_to_address(address_hash), + {:recaptcha, true} <- {:recaptcha, captcha_helper().recaptcha_passed?(recaptcha_response)} 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) - ) + |> csv_export_module.export(from_period, to_period) + |> Enum.reduce_while(put_resp_params(conn, file_name), fn chunk, conn -> + case Conn.chunk(conn, chunk) do + {:ok, conn} -> + {:cont, conn} + + {:error, :closed} -> + {:halt, conn} + end + end) else :error -> unprocessable_entity(conn) {:error, :not_found} -> not_found(conn) + + {:recaptcha, false} -> + not_found(conn) end end - def token_transfers_csv(conn, _), do: not_found(conn) + defp items_csv(conn, _, _, _), do: not_found(conn) + + def token_transfers_csv(conn, params) do + items_csv( + conn, + %{ + "address_id" => params["address_id"], + "from_period" => params["from_period"], + "to_period" => params["to_period"], + "recaptcha_response" => params["recaptcha_response"] + }, + AddressTokenTransferCsvExporter, + "token_transfers.csv" + ) + end def transactions_csv(conn, %{ "address_id" => address_hash_string, "from_period" => from_period, - "to_period" => to_period + "to_period" => to_period, + "recaptcha_response" => recaptcha_response }) 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 + items_csv( + conn, + %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => recaptcha_response + }, + AddressTransactionCsvExporter, + "transactions.csv" + ) 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 + "to_period" => to_period, + "recaptcha_response" => recaptcha_response }) 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 + items_csv( + conn, + %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => recaptcha_response + }, + AddressInternalTransactionCsvExporter, + "internal_transactions.csv" + ) 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 + def logs_csv(conn, %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => recaptcha_response + }) do + items_csv( + conn, + %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => recaptcha_response + }, + AddressLogCsvExporter, + "logs.csv" + ) end - - def logs_csv(conn, _), do: not_found(conn) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex index 6f6ccdcad7ab..3963c262890c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex @@ -4,9 +4,13 @@ defmodule BlockScoutWeb.AddressValidationController do """ use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{AccessHelpers, BlockView, Controller} alias Explorer.ExchangeRates.Token alias Explorer.{Chain, Market} @@ -79,7 +83,8 @@ defmodule BlockScoutWeb.AddressValidationController do coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), current_path: Controller.current_full_path(conn), counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex index f3b50dd4a26e..1accce5f5cc5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex @@ -8,7 +8,11 @@ defmodule BlockScoutWeb.AddressWriteContractController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.AccessHelpers + alias BlockScoutWeb.AddressView alias Explorer.{Chain, Market} alias Explorer.Chain.Address alias Explorer.ExchangeRates.Token @@ -25,6 +29,15 @@ defmodule BlockScoutWeb.AddressWriteContractController do } ] + custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address_hash_string) + + base_params = [ + type: :regular, + action: :write, + custom_abi: custom_abi?, + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + ] + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true), false <- is_nil(address.smart_contract), @@ -32,16 +45,40 @@ defmodule BlockScoutWeb.AddressWriteContractController do render( conn, "index.html", - address: address, - type: :regular, - action: :write, - coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + base_params ++ + [ + address: address, + non_custom_abi: true, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) + ] ) else _ -> - not_found(conn) + if custom_abi? do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.find_contract_address(address_hash, address_options, false), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + base_params ++ + [ + address: address, + non_custom_abi: false, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) + ] + ) + else + _ -> + not_found(conn) + end + else + not_found(conn) + end end end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex index e82fc17c29fb..fc5a7c713345 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex @@ -2,6 +2,9 @@ defmodule BlockScoutWeb.AddressWriteProxyController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.AccessHelpers alias Explorer.{Chain, Market} alias Explorer.Chain.Address @@ -31,7 +34,8 @@ defmodule BlockScoutWeb.AddressWriteProxyController do action: :write, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) else _ -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex index 54ef51a4e4f7..394c3018e8a3 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -249,11 +249,11 @@ defmodule BlockScoutWeb.API.RPC.AddressController do %{} |> put_order_by_direction(params) |> Helpers.put_pagination_options(params) - |> put_start_block(params) - |> put_end_block(params) + |> put_block(params, "start_block") + |> put_block(params, "end_block") |> put_filter_by(params) - |> put_start_timestamp(params) - |> put_end_timestamp(params) + |> put_timestamp(params, "start_timestamp") + |> put_timestamp(params, "end_timestamp") end @doc """ @@ -427,52 +427,39 @@ defmodule BlockScoutWeb.API.RPC.AddressController do end end - defp put_start_block(options, params) do - with %{"startblock" => startblock_param} <- params, - {start_block, ""} <- Integer.parse(startblock_param) do - Map.put(options, :start_block, start_block) - else - _ -> - options - end - end - - defp put_end_block(options, params) do - with %{"endblock" => endblock_param} <- params, - {end_block, ""} <- Integer.parse(endblock_param) do - Map.put(options, :end_block, end_block) + # sobelow_skip ["DOS.StringToAtom"] + defp put_block(options, params, key) do + with %{^key => block_param} <- params, + {block_number, ""} <- Integer.parse(block_param) do + Map.put(options, String.to_atom(key), block_number) else _ -> options end end + # sobelow_skip ["DOS.StringToAtom"] defp put_filter_by(options, params) do case params do - %{"filterby" => filter_by} when filter_by in ["from", "to"] -> - Map.put(options, :filter_by, filter_by) + %{"filter_by" => filter_by} when filter_by in ["from", "to"] -> + Map.put(options, String.to_atom("filter_by"), filter_by) _ -> options end end - defp put_start_timestamp(options, params) do - with %{"starttimestamp" => starttimestamp_param} <- params, - {unix_timestamp, ""} <- Integer.parse(starttimestamp_param), - {:ok, start_timestamp} <- DateTime.from_unix(unix_timestamp) do - Map.put(options, :start_timestamp, start_timestamp) - else - _ -> - options - end + def put_timestamp({:ok, options}, params, timestamp_param_key) do + options = put_timestamp(options, params, timestamp_param_key) + {:ok, options} end - defp put_end_timestamp(options, params) do - with %{"endtimestamp" => endtimestamp_param} <- params, - {unix_timestamp, ""} <- Integer.parse(endtimestamp_param), - {:ok, end_timestamp} <- DateTime.from_unix(unix_timestamp) do - Map.put(options, :end_timestamp, end_timestamp) + # sobelow_skip ["DOS.StringToAtom"] + def put_timestamp(options, params, timestamp_param_key) do + with %{^timestamp_param_key => timestamp_param} <- params, + {unix_timestamp, ""} <- Integer.parse(timestamp_param), + {:ok, timestamp} <- DateTime.from_unix(unix_timestamp) do + Map.put(options, String.to_atom(timestamp_param_key), timestamp) else _ -> options 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 a2be1f334379..cc19805139d7 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 @@ -4,12 +4,13 @@ defmodule BlockScoutWeb.API.RPC.ContractController do require Logger alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController - alias BlockScoutWeb.API.RPC.Helpers + alias BlockScoutWeb.API.RPC.{AddressController, Helpers} alias Explorer.Chain alias Explorer.Chain.Events.Publisher, as: EventsPublisher - alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.{Address, Hash, SmartContract} alias Explorer.Chain.SmartContract.VerificationStatus alias Explorer.Etherscan.Contracts + alias Explorer.SmartContract.Helper alias Explorer.SmartContract.Solidity.Publisher alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker alias Explorer.SmartContract.Vyper.Publisher, as: VyperPublisher @@ -119,7 +120,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do {:format, {:ok, _casted_address_hash}} <- to_address_hash(address_hash), {:params, {:ok, fetched_params}} <- {:params, fetch_verifysourcecode_params(params)}, uid <- VerificationStatus.generate_uid(address_hash) do - Que.add(SolidityPublisherWorker, {fetched_params, json_input, uid}) + Que.add(SolidityPublisherWorker, {"json_api", fetched_params, json_input, uid}) render(conn, :show, %{result: uid}) else @@ -180,11 +181,11 @@ defmodule BlockScoutWeb.API.RPC.ContractController do jsons = files_array - |> Enum.filter(fn file -> only_json(file.filename) end) + |> Enum.filter(fn file -> Helper.json_file?(file.filename) end) sols = files_array - |> Enum.filter(fn file -> only_sol(file.filename) end) + |> Enum.filter(fn file -> Helper.sol_file?(file.filename) end) if length(jsons) > 0 and length(sols) > 0 do {:ok, files_array} @@ -207,26 +208,6 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end end - defp only_sol(filename) do - case List.last(String.split(String.downcase(filename), ".")) do - "sol" -> - true - - _ -> - false - end - end - - defp only_json(filename) do - case List.last(String.split(String.downcase(filename), ".")) do - "json" -> - true - - _ -> - false - end - end - defp get_metadata_and_publish(address_hash_string, conn) do case Sourcify.get_metadata(address_hash_string) do {:ok, verification_metadata} -> @@ -423,7 +404,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do address = Contracts.address_hash_to_address_with_source_code(address_hash) render(conn, :getsourcecode, %{ - contract: address + contract: address || %Address{hash: address_hash, smart_contract: nil} }) else {:address_param, :error} -> @@ -431,9 +412,6 @@ defmodule BlockScoutWeb.API.RPC.ContractController do {:format, :error} -> render(conn, :error, error: @invalid_address) - - {:contract, :not_found} -> - render(conn, :getsourcecode, %{contract: nil, address_hash: nil}) end end @@ -442,7 +420,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do case Map.get(opts, :filter) do :verified -> - Contracts.list_verified_contracts(page_size, offset) + Contracts.list_verified_contracts(page_size, offset, opts) :decompiled -> not_decompiled_with_version = Map.get(opts, :not_decompiled_with_version) @@ -465,7 +443,9 @@ defmodule BlockScoutWeb.API.RPC.ContractController do defp add_filters(options, params) do options |> add_filter(params) - |> add_not_decompiled_with_version(params) + |> add_param(params, :not_decompiled_with_version) + |> AddressController.put_timestamp(params, "verified_at_start_timestamp") + |> AddressController.put_timestamp(params, "verified_at_end_timestamp") end defp add_filter(options, params) do @@ -478,14 +458,14 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end end - defp add_not_decompiled_with_version({:ok, options}, params) do - case Map.fetch(params, "not_decompiled_with_version") do - {:ok, value} -> {:ok, Map.put(options, :not_decompiled_with_version, value)} + defp add_param({:ok, options}, params, key) do + case Map.fetch(params, Atom.to_string(key)) do + {:ok, value} -> {:ok, Map.put(options, key, value)} :error -> {:ok, options} end end - defp add_not_decompiled_with_version(options, _params) do + defp add_param(options, _params, _key) do options end @@ -590,7 +570,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do defp parse_optimization_runs(other), do: other defp fetch_external_libraries(params) do - Enum.reduce(1..10, %{}, fn number, acc -> + Enum.reduce(1..Application.get_env(:block_scout_web, :verification_max_libraries), %{}, fn number, acc -> case Map.fetch(params, "library#{number}Name") do {:ok, library_name} -> library_address = Map.get(params, "library#{number}Address") diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex index 7471209b9a0f..5e57d5162001 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex @@ -24,19 +24,9 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do alias Phoenix.Controller alias Plug.Conn - APILogger.message( - "Current global API rate limit #{inspect(Application.get_env(:block_scout_web, :api_rate_limit)[:global_limit])} reqs/sec" - ) - - APILogger.message( - "Current API rate limit by key #{inspect(Application.get_env(:block_scout_web, :api_rate_limit)[:limit_by_key])} reqs/sec" - ) - - APILogger.message( - "Current API rate limit by IP #{inspect(Application.get_env(:block_scout_web, :api_rate_limit)[:limit_by_ip])} reqs/sec" - ) - - def init(opts), do: opts + def init(opts) do + opts + end def call(%Conn{params: %{"module" => module, "action" => action}} = conn, translations) do with true <- valid_api_request_path(conn), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex index 008af99de07b..8c6d995ee046 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.StatsController do use Explorer.Schema + alias Explorer alias Explorer.{Chain, Etherscan, ExchangeRates} alias Explorer.Chain.Cache.{AddressSum, AddressSumMinusBurnt} alias Explorer.Chain.Wei @@ -60,7 +61,7 @@ defmodule BlockScoutWeb.API.RPC.StatsController do end def coinprice(conn, _params) do - symbol = Application.get_env(:explorer, :coin) + symbol = Explorer.coin() rates = ExchangeRates.lookup(symbol) render(conn, "coinprice.json", rates: rates) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex index 578c21f77a94..52352d55d4cc 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.V1.HealthController do alias BlockScoutWeb.API.APILogger alias Explorer.Chain + alias Timex.Duration def health(conn, _) do APILogger.log(conn) @@ -39,12 +40,20 @@ defmodule BlockScoutWeb.API.V1.HealthController do end def error({:error, number, timestamp}) do + healthy_blocks_period = Application.get_env(:explorer, :healthy_blocks_period) + + healthy_blocks_period_formatted = + healthy_blocks_period + |> Duration.from_milliseconds() + |> Duration.to_minutes() + |> trunc() + %{ "healthy" => false, "error_code" => 5001, "error_title" => "blocks fetching is stuck", "error_description" => - "There are no new blocks in the DB for the last 5 mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance", + "There are no new blocks in the DB for the last #{healthy_blocks_period_formatted} mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance", "data" => %{ "latest_block_number" => to_string(number), "latest_block_inserted_at" => to_string(timestamp) 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 65e670e8325f..6794b519b0ad 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 @@ -55,7 +55,10 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do end defp fetch_external_libraries(params) do - keys = Enum.flat_map(1..10, fn i -> ["library#{i}_name", "library#{i}_address"] end) + keys = + Enum.flat_map(1..Application.get_env(:block_scout_web, :verification_max_libraries), fn i -> + ["library#{i}_name", "library#{i}_address"] + end) Map.take(params, keys) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex new file mode 100644 index 000000000000..806fcd0f374a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -0,0 +1,270 @@ +defmodule BlockScoutWeb.API.V2.AddressController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [ + next_page_params: 3, + paging_options: 1, + split_list_by_page: 1, + current_filter: 1 + ] + + import BlockScoutWeb.PagingHelper, + only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1] + + alias BlockScoutWeb.AccessHelpers + alias BlockScoutWeb.API.V2.{AddressView, BlockView, TransactionView} + alias Explorer.{Chain, Market} + alias Indexer.Fetcher.TokenBalanceOnDemand + + @transaction_necessity_by_association [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + :block => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + } + ] + + @token_transfer_necessity_by_association [ + necessity_by_association: %{ + :to_address => :optional, + :from_address => :optional, + :block => :optional + } + ] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + def address(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash)} do + conn + |> put_status(200) + |> render(:address, %{address: address}) + end + end + + def counters(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash)} do + {validation_count} = Chain.address_counters(address) + + transactions_from_db = address.transactions_count || 0 + token_transfers_from_db = address.token_transfers_count || 0 + address_gas_usage_from_db = address.gas_used || 0 + + json(conn, %{ + transactions_count: to_string(transactions_from_db), + token_transfers_count: to_string(token_transfers_from_db), + gas_usage_count: to_string(address_gas_usage_from_db), + validations_count: to_string(validation_count) + }) + end + end + + def token_balances(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + token_balances = + address_hash + |> Chain.fetch_last_token_balances() + + Task.start_link(fn -> + TokenBalanceOnDemand.trigger_fetch(address_hash, token_balances) + end) + + token_balances_with_price = + token_balances + |> Market.add_price() + + conn + |> put_status(200) + |> render(:token_balances, %{token_balances: token_balances_with_price}) + end + end + + def transactions(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + options = + @transaction_necessity_by_association + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(current_filter(params)) + + results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options) + {transactions, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page |> next_page_params(transactions, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + end + end + + def token_transfers(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + options = + @token_transfer_necessity_by_association + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(current_filter(params)) + |> Keyword.merge(token_transfers_types_options(params)) + + results_plus_one = + Chain.address_hash_to_token_transfers_new( + address_hash, + options + ) + + {token_transfers, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page |> next_page_params(token_transfers, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + end + end + + def internal_transactions(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + full_options = + [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + } + ] + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(current_filter(params)) + + results_plus_one = Chain.address_to_internal_transactions(address_hash, full_options) + {internal_transactions, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page |> next_page_params(internal_transactions, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:internal_transactions, %{ + internal_transactions: internal_transactions, + next_page_params: next_page_params + }) + end + end + + def logs(conn, %{"address_hash" => address_hash_string, "topic" => topic} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + prepared_topic = String.trim(topic) + + formatted_topic = if String.starts_with?(prepared_topic, "0x"), do: prepared_topic, else: "0x" <> prepared_topic + + results_plus_one = Chain.address_to_logs(address_hash, topic: formatted_topic) + + {logs, next_page} = split_list_by_page(results_plus_one) + + next_page_params = next_page |> next_page_params(logs, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:logs, %{logs: logs, next_page_params: next_page_params}) + end + end + + def logs(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + results_plus_one = Chain.address_to_logs(address_hash, paging_options(params)) + {logs, next_page} = split_list_by_page(results_plus_one) + + next_page_params = next_page |> next_page_params(logs, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:logs, %{logs: logs, next_page_params: next_page_params}) + end + end + + def blocks_validated(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + full_options = + Keyword.merge( + [ + necessity_by_association: %{ + miner: :required, + nephews: :optional, + transactions: :optional, + rewards: :optional + } + ], + paging_options(params) + ) + + results_plus_one = Chain.get_blocks_validated_by_address(full_options, address_hash) + {blocks, next_page} = split_list_by_page(results_plus_one) + + next_page_params = next_page |> next_page_params(blocks, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(BlockView) + |> render(:blocks, %{blocks: blocks, next_page_params: next_page_params}) + end + end + + def coin_balance_history(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + {:not_found, {:ok, _address}, _} <- + {:not_found, Chain.hash_to_address(address_hash), :empty_items_with_next_page_params} do + full_options = paging_options(params) + + results_plus_one = Chain.address_to_coin_balances(address_hash, full_options) + + {coin_balances, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page |> next_page_params(coin_balances, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(AddressView) + |> render(:coin_balances, %{coin_balances: coin_balances, next_page_params: next_page_params}) + end + end + + def coin_balance_history_by_day(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + balances_by_day = + address_hash + |> Chain.address_to_balances_by_day(true) + + conn + |> put_status(200) + |> put_view(AddressView) + |> render(:coin_balances_by_day, %{coin_balances_by_day: balances_by_day}) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex new file mode 100644 index 000000000000..10533c1f0b59 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -0,0 +1,84 @@ +defmodule BlockScoutWeb.API.V2.BlockController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [next_page_params: 3, paging_options: 1, put_key_value_to_paging_options: 3, split_list_by_page: 1] + + import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1, select_block_type: 1] + + alias BlockScoutWeb.API.V2.TransactionView + alias BlockScoutWeb.BlockTransactionController + alias Explorer.Chain + + @transaction_necessity_by_association [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + :block => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + } + ] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + def block(conn, %{"block_hash_or_number" => block_hash_or_number}) do + with {:ok, block} <- + BlockTransactionController.param_block_hash_or_number_to_block(block_hash_or_number, + necessity_by_association: %{ + [miner: :names] => :required, + :uncles => :optional, + :nephews => :optional, + :rewards => :optional, + :transactions => :optional + } + ) do + conn + |> put_status(200) + |> render(:block, %{block: block}) + end + end + + def blocks(conn, params) do + full_options = select_block_type(params) + + blocks_plus_one = + full_options + |> Keyword.merge(paging_options(params)) + |> Chain.list_blocks() + + {blocks, next_page} = split_list_by_page(blocks_plus_one) + + next_page_params = next_page |> next_page_params(blocks, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> render(:blocks, %{blocks: blocks, next_page_params: next_page_params}) + end + + def transactions(conn, %{"block_hash_or_number" => block_hash_or_number} = params) do + with {:ok, block} <- BlockTransactionController.param_block_hash_or_number_to_block(block_hash_or_number, []) do + full_options = + Keyword.merge( + @transaction_necessity_by_association, + 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, false) + + {transactions, next_page} = split_list_by_page(transactions_plus_one) + + next_page_params = + next_page + |> next_page_params(transactions, params) + |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/config_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/config_controller.ex new file mode 100644 index 000000000000..9d76eee6748d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/config_controller.ex @@ -0,0 +1,11 @@ +defmodule BlockScoutWeb.API.V2.ConfigController do + use BlockScoutWeb, :controller + + def json_rpc_url(conn, _params) do + json_rpc_url = Application.get_env(:block_scout_web, :json_rpc) + + conn + |> put_status(200) + |> render(:json_rpc_url, %{url: json_rpc_url}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex new file mode 100644 index 000000000000..d808d6aa0e27 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -0,0 +1,50 @@ +defmodule BlockScoutWeb.API.V2.FallbackController do + use Phoenix.Controller + + alias BlockScoutWeb.API.V2.ApiView + + def call(conn, {:format, _}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(ApiView) + |> render(:message, %{message: "Invalid parameter(s)"}) + end + + def call(conn, {:not_found, _, :empty_items_with_next_page_params}) do + conn + |> json(%{"items" => [], "next_page_params" => nil}) + end + + def call(conn, {:not_found, _}) do + conn + |> put_status(:not_found) + |> put_view(ApiView) + |> render(:message, %{message: "Not found"}) + end + + def call(conn, {:error, {:invalid, :hash}}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(ApiView) + |> render(:message, %{message: "Invalid hash"}) + end + + def call(conn, {:error, {:invalid, :number}}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(ApiView) + |> render(:message, %{message: "Invalid number"}) + end + + def call(conn, {:error, :not_found}) do + conn + |> call({:not_found, nil}) + end + + def call(conn, {:restricted_access, true}) do + conn + |> put_status(:forbidden) + |> put_view(ApiView) + |> render(:message, %{message: "Restricted access"}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex new file mode 100644 index 000000000000..1851cd9a3ea9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex @@ -0,0 +1,52 @@ +defmodule BlockScoutWeb.API.V2.MainPageController do + use Phoenix.Controller + + alias Explorer.{Chain, PagingOptions} + alias BlockScoutWeb.API.V2.{BlockView, TransactionView} + alias Explorer.{Chain, Repo} + + def blocks(conn, _params) do + blocks = + [paging_options: %PagingOptions{page_size: 4}] + |> Chain.list_blocks() + |> Repo.preload([[miner: :names], :transactions, :rewards]) + + conn + |> put_status(200) + |> put_view(BlockView) + |> render(:blocks, %{blocks: blocks}) + end + + def transactions(conn, _params) do + recent_transactions = + Chain.recent_collated_transactions(false, + necessity_by_association: %{ + :block => :required, + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + }, + paging_options: %PagingOptions{page_size: 6} + ) + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:transactions, %{transactions: recent_transactions}) + end + + def indexing_status(conn, _params) do + indexed_ratio_blocks = Chain.indexed_ratio_blocks() + finished_indexing_blocks = Chain.finished_blocks_indexing?(indexed_ratio_blocks) + + json(conn, %{ + finished_indexing_blocks: finished_indexing_blocks, + finished_indexing: Chain.finished_indexing?(indexed_ratio_blocks), + indexed_blocks_ratio: indexed_ratio_blocks, + indexed_inernal_transactions_ratio: if(finished_indexing_blocks, do: Chain.indexed_ratio_internal_transactions()) + }) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex new file mode 100644 index 000000000000..59b06a3d774f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex @@ -0,0 +1,24 @@ +defmodule BlockScoutWeb.API.V2.SearchController do + use Phoenix.Controller + + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias Explorer.Chain + + def search(conn, %{"q" => query} = params) do + [paging_options: paging_options] = paging_options(params) + offset = (max(paging_options.page_number, 1) - 1) * paging_options.page_size + + search_results_plus_one = + paging_options + |> Chain.joint_search(offset, query) + + {search_results, next_page} = split_list_by_page(search_results_plus_one) + + next_page_params = next_page_params(next_page, search_results, params) + + conn + |> put_status(200) + |> render(:search_results, %{search_results: search_results, next_page_params: next_page_params}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex new file mode 100644 index 000000000000..7624d3574cd1 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -0,0 +1,116 @@ +defmodule BlockScoutWeb.API.V2.StatsController do + use Phoenix.Controller + + alias BlockScoutWeb.API.V2.Helper + alias Explorer.{Chain, Market} + alias Explorer.Chain.Cache.Block, as: BlockCache + alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage} + alias Explorer.Chain.Cache.Transaction, as: TransactionCache + alias Explorer.Chain.Supply.RSK + alias Explorer.Chain.Transaction.History.TransactionStats + alias Explorer.Counters.AverageBlockTime + alias Explorer.ExchangeRates.Token + alias Timex.Duration + + def stats(conn, _params) do + market_cap_type = + case Application.get_env(:explorer, :supply) do + RSK -> + RSK + + _ -> + :standard + end + + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + + transaction_stats = Helper.get_transaction_stats() + + gas_prices = + case GasPriceOracle.get_gas_prices() do + {:ok, gas_prices} -> + gas_prices + + _ -> + nil + end + + gas_price = Application.get_env(:block_scout_web, :gas_price) + + json( + conn, + %{ + "total_blocks" => BlockCache.estimated_count() |> to_string(), + "total_addresses" => Chain.address_estimated_count() |> to_string(), + "total_transactions" => TransactionCache.estimated_count() |> to_string(), + "average_block_time" => AverageBlockTime.average_block_time() |> Duration.to_milliseconds(), + "coin_price" => exchange_rate.usd_value, + "total_gas_used" => GasUsage.total() |> to_string(), + "transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(), + "gas_used_today" => Enum.at(transaction_stats, 0).gas_used, + "gas_prices" => gas_prices, + "static_gas_price" => gas_price, + "market_cap" => Helper.market_cap(market_cap_type, exchange_rate), + "network_utilization_percentage" => network_utilization_percentage() + } + ) + end + + defp network_utilization_percentage do + {gas_used, gas_limit} = + Enum.reduce(Chain.list_blocks(), {Decimal.new(0), Decimal.new(0)}, fn block, {gas_used, gas_limit} -> + {Decimal.add(gas_used, block.gas_used), Decimal.add(gas_limit, block.gas_limit)} + end) + + if Decimal.compare(gas_limit, 0) == :eq, + do: 0, + else: gas_used |> Decimal.div(gas_limit) |> Decimal.mult(100) |> Decimal.to_float() + end + + def transactions_chart(conn, _params) do + [{:history_size, history_size}] = + Application.get_env(:block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, [{:history_size, 30}]) + + today = Date.utc_today() + latest = Date.add(today, -1) + earliest = Date.add(latest, -1 * history_size) + + date_range = TransactionStats.by_date_range(earliest, latest) + + transaction_history_data = + date_range + |> Enum.map(fn row -> + %{date: row.date, tx_count: row.number_of_transactions} + end) + + json(conn, %{ + chart_data: transaction_history_data + }) + end + + def market_chart(conn, _params) do + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + + recent_market_history = Market.fetch_recent_history() + + market_history_data = + recent_market_history + |> case do + [today | the_rest] -> + [%{today | closing_price: exchange_rate.usd_value} | the_rest] + + data -> + data + end + |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end) + + json(conn, %{ + chart_data: market_history_data, + available_supply: available_supply(Chain.supply_for_days(), exchange_rate) + }) + end + + defp available_supply(:ok, exchange_rate), do: exchange_rate.available_supply || 0 + + defp available_supply({:ok, supply_for_days}, _exchange_rate), do: supply_for_days +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex new file mode 100644 index 000000000000..b13b265a7e55 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -0,0 +1,68 @@ +defmodule BlockScoutWeb.API.V2.TokenController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias BlockScoutWeb.API.V2.TransactionView + alias Explorer.Chain + + import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] + import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + def token(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash)} do + conn + |> put_status(200) + |> render(:token, %{token: token}) + end + end + + def counters(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + {:not_found, {:ok, _}} <- {:not_found, Chain.token_from_address_hash(address_hash)} do + {transfer_count, token_holder_count} = Chain.fetch_token_counters(address_hash, 30_000) + + json(conn, %{transfers_count: to_string(transfer_count), token_holders_count: to_string(token_holder_count)}) + end + end + + def transfers(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + results_plus_one = Chain.fetch_token_transfers_from_token_hash(address_hash, paging_options(params)) + + {token_transfers, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page |> next_page_params(token_transfers, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + end + end + + def holders(conn, %{"address_hash" => address_hash_string} = params) do + from_api = true + + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + {:not_found, {:ok, token}, _} <- + {:not_found, Chain.token_from_address_hash(address_hash), :empty_items_with_next_page_params} do + results_plus_one = Chain.fetch_token_holders_from_token_hash(address_hash, from_api, paging_options(params)) + {token_balances, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page |> next_page_params(token_balances, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> render(:token_balances, %{token_balances: token_balances, next_page_params: next_page_params, token: token}) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex new file mode 100644 index 000000000000..b66c5726cf93 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -0,0 +1,258 @@ +defmodule BlockScoutWeb.API.V2.TransactionController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1] + + import BlockScoutWeb.PagingHelper, + only: [ + delete_parameters_from_next_page_params: 1, + paging_options: 2, + filter_options: 2, + method_filter_options: 1, + token_transfers_types_options: 1, + type_filter_options: 1 + ] + + alias BlockScoutWeb.AccessHelpers + alias Explorer.Chain + alias Explorer.Chain.Import + alias Explorer.Chain.Import.Runner.InternalTransactions + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @transaction_necessity_by_association %{ + :block => :optional, + [created_contract_address: :names] => :optional, + [created_contract_address: :token] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [to_address: :smart_contract] => :optional + } + + @token_transfers_neccessity_by_association %{ + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + from_address: :required, + to_address: :required + } + + @token_transfers_in_tx_neccessity_by_association %{ + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + from_address: :required, + to_address: :required, + token: :required + } + + @internal_transaction_neccessity_by_association [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [transaction: :block] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + } + ] + + def transaction(conn, %{"transaction_hash" => transaction_hash_string} = params) do + with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:not_found, {:ok, transaction}} <- + {:not_found, + Chain.hash_to_transaction( + transaction_hash, + necessity_by_association: @transaction_necessity_by_association + )}, + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.to_address_hash), params), + preloaded <- + Chain.preload_token_transfers(transaction, @token_transfers_in_tx_neccessity_by_association, false) do + conn + |> put_status(200) + |> render(:transaction, %{transaction: preloaded}) + end + end + + def transactions(conn, params) do + filter_options = filter_options(params, :validated) + + full_options = + [ + necessity_by_association: @transaction_necessity_by_association + ] + |> Keyword.merge(paging_options(params, filter_options)) + |> Keyword.merge(method_filter_options(params)) + |> Keyword.merge(type_filter_options(params)) + + transactions_plus_one = Chain.recent_transactions(full_options, filter_options) + + {transactions, next_page} = split_list_by_page(transactions_plus_one) + + next_page_params = next_page |> next_page_params(transactions, params) |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + end + + def raw_trace(conn, %{"transaction_hash" => transaction_hash_string} = params) do + with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:not_found, {:ok, transaction}} <- + {:not_found, Chain.hash_to_transaction(transaction_hash)}, + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.to_address_hash), params) do + if is_nil(transaction.block_number) do + conn + |> put_status(200) + |> render(:raw_trace, %{internal_transactions: []}) + else + internal_transactions = Chain.all_transaction_to_internal_transactions(transaction_hash) + + first_trace_exists = + Enum.find_index(internal_transactions, fn trace -> + trace.index == 0 + end) + + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + internal_transactions = + if first_trace_exists do + internal_transactions + else + response = + Chain.fetch_first_trace( + [ + %{ + block_hash: transaction.block_hash, + block_number: transaction.block_number, + hash_data: transaction_hash_string, + transaction_index: transaction.index + } + ], + json_rpc_named_arguments + ) + + case response do + {:ok, first_trace_params} -> + InternalTransactions.run_insert_only(first_trace_params, %{ + timeout: :infinity, + timestamps: Import.timestamps(), + internal_transactions: %{params: first_trace_params} + }) + + Chain.all_transaction_to_internal_transactions(transaction_hash) + + {:error, _} -> + internal_transactions + + :ignore -> + internal_transactions + end + end + + conn + |> put_status(200) + |> render(:raw_trace, %{internal_transactions: internal_transactions}) + end + end + end + + def token_transfers(conn, %{"transaction_hash" => transaction_hash_string} = params) do + with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:not_found, {:ok, transaction}} <- + {:not_found, Chain.hash_to_transaction(transaction_hash)}, + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.to_address_hash), params) do + full_options = + [necessity_by_association: @token_transfers_neccessity_by_association] + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(token_transfers_types_options(params)) + + token_transfers_plus_one = Chain.transaction_to_token_transfers(transaction_hash, full_options) + + {token_transfers, next_page} = split_list_by_page(token_transfers_plus_one) + + next_page_params = + next_page + |> next_page_params(token_transfers, params) + |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + end + end + + def internal_transactions(conn, %{"transaction_hash" => transaction_hash_string} = params) do + with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:not_found, {:ok, transaction}} <- + {:not_found, Chain.hash_to_transaction(transaction_hash)}, + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.to_address_hash), params) do + full_options = + Keyword.merge( + @internal_transaction_neccessity_by_association, + paging_options(params) + ) + + internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction_hash, full_options) + + {internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one) + + next_page_params = + next_page + |> next_page_params(internal_transactions, params) + |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> render(:internal_transactions, %{ + internal_transactions: internal_transactions, + next_page_params: next_page_params + }) + end + end + + def logs(conn, %{"transaction_hash" => transaction_hash_string} = params) do + with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:not_found, {:ok, transaction}} <- + {:not_found, Chain.hash_to_transaction(transaction_hash)}, + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- AccessHelpers.restricted_access?(to_string(transaction.to_address_hash), params) do + full_options = + Keyword.merge( + [ + necessity_by_association: %{ + [address: :names] => :optional, + [address: :smart_contract] => :optional, + address: :optional + } + ], + paging_options(params) + ) + + from_api = true + logs_plus_one = Chain.transaction_to_logs(transaction_hash, from_api, full_options) + + {logs, next_page} = split_list_by_page(logs_plus_one) + + next_page_params = + next_page + |> next_page_params(logs, params) + |> delete_parameters_from_next_page_params() + + conn + |> put_status(200) + |> render(:logs, %{ + tx_hash: transaction_hash, + logs: logs, + next_page_params: next_page_params + }) + end + end +end 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 9602aae55418..facda14c9254 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 @@ -133,7 +133,7 @@ defmodule BlockScoutWeb.BlockTransactionController do end end - defp param_block_hash_or_number_to_block("0x" <> _ = param, options) do + def param_block_hash_or_number_to_block("0x" <> _ = param, options) do case string_to_block_hash(param) do {:ok, hash} -> hash_to_block(hash, options) @@ -143,8 +143,8 @@ defmodule BlockScoutWeb.BlockTransactionController do end end - defp param_block_hash_or_number_to_block(number_string, options) - when is_binary(number_string) do + def param_block_hash_or_number_to_block(number_string, options) + when is_binary(number_string) do case BlockScoutWeb.Chain.param_to_block_number(number_string) do {:ok, number} -> number_to_block(number, 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 deleted file mode 100644 index ad170262a6e9..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/controllers/captcha_cotroller.ex +++ /dev/null @@ -1,16 +0,0 @@ -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/chain/transaction_history_chart_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex index a52a6dd750e0..dec6f4f90ee3 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex @@ -5,7 +5,7 @@ defmodule BlockScoutWeb.Chain.TransactionHistoryChartController do def show(conn, _params) do if ajax?(conn) do - [{:history_size, history_size}] = Application.get_env(:block_scout_web, __MODULE__, 30) + [{:history_size, history_size}] = Application.get_env(:block_scout_web, __MODULE__, [{:history_size, 30}]) today = Date.utc_today() latest = Date.add(today, -1) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 6d112bdc45da..61ac896135b1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.ChainController do import BlockScoutWeb.Chain, only: [paging_options: 1] + alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.{ChainView, Controller} alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.Chain.{Address, Block, Transaction} @@ -10,7 +11,6 @@ defmodule BlockScoutWeb.ChainController do alias Explorer.Chain.Cache.GasUsage alias Explorer.Chain.Cache.Transaction, as: TransactionCache alias Explorer.Chain.Supply.RSK - alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime alias Explorer.ExchangeRates.Token alias Explorer.Market @@ -33,7 +33,7 @@ defmodule BlockScoutWeb.ChainController do exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() - transaction_stats = get_transaction_stats() + transaction_stats = Helper.get_transaction_stats() chart_data_paths = %{ market: market_history_chart_path(conn, :show), @@ -61,25 +61,6 @@ defmodule BlockScoutWeb.ChainController do ) end - def get_transaction_stats do - stats_scale = date_range(1) - transaction_stats = TransactionStats.by_date_range(stats_scale.earliest, stats_scale.latest) - - # Need datapoint for legend if none currently available. - if Enum.empty?(transaction_stats) do - [%{number_of_transactions: 0, gas_used: 0}] - else - transaction_stats - end - end - - def date_range(num_days) do - today = Date.utc_today() - latest = Date.add(today, -1) - x_days_back = Date.add(latest, -1 * (num_days - 1)) - %{earliest: x_days_back, latest: latest} - end - def search(conn, %{"q" => ""}) do show(conn, []) end @@ -110,7 +91,7 @@ defmodule BlockScoutWeb.ChainController do results = paging_options - |> search_by(offset, term) + |> Chain.joint_search(offset, term) encoded_results = results @@ -144,10 +125,6 @@ defmodule BlockScoutWeb.ChainController do json(conn, "{}") end - def search_by(paging_options, offset, term) do - Chain.joint_search(paging_options, offset, term) - end - def chain_blocks(conn, _params) do if ajax?(conn) do blocks = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/pagination_helpers.ex b/apps/block_scout_web/lib/block_scout_web/controllers/pagination_helpers.ex deleted file mode 100644 index e5ad7838bdf3..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/controllers/pagination_helpers.ex +++ /dev/null @@ -1,20 +0,0 @@ -defmodule BlockScoutWeb.PaginationHelpers do - @moduledoc """ - Common pagination logic helpers. - """ - - def current_page_number(params) do - cond do - !params["prev_page_number"] -> 1 - params["next_page"] -> String.to_integer(params["prev_page_number"]) + 1 - params["prev_page"] -> String.to_integer(params["prev_page_number"]) - 1 - end - end - - def add_navigation_params(params, current_page_path, current_page_number) do - params - |> Map.put("prev_page_path", current_page_path) - |> Map.put("next_page", true) - |> Map.put("prev_page_number", current_page_number) - end -end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex index 55fe550745bb..906fbc3194b4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex @@ -63,7 +63,7 @@ defmodule BlockScoutWeb.PendingTransactionController do end defp get_pending_transactions_and_next_page(options) do - transactions_plus_one = Chain.recent_pending_transactions(options) + transactions_plus_one = Chain.recent_pending_transactions(options, true) split_list_by_page(transactions_plus_one) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex index 714c9e351d20..d692d6de1471 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex @@ -11,7 +11,7 @@ defmodule BlockScoutWeb.RecentTransactionsController do def index(conn, _params) do if ajax?(conn) do recent_transactions = - Chain.recent_collated_transactions( + Chain.recent_collated_transactions(true, necessity_by_association: %{ :block => :required, [created_contract_address: :names] => :optional, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex index 3e470f20bbb3..b2f639a6e3a9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex @@ -3,7 +3,8 @@ defmodule BlockScoutWeb.SearchController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] - alias BlockScoutWeb.{ChainController, Controller, SearchView} + alias BlockScoutWeb.{Controller, SearchView} + alias Explorer.Chain alias Phoenix.View def search_results(conn, %{"q" => query, "type" => "JSON"} = params) do @@ -12,7 +13,7 @@ defmodule BlockScoutWeb.SearchController do search_results_plus_one = paging_options - |> ChainController.search_by(offset, query) + |> Chain.joint_search(offset, query) {search_results, next_page} = split_list_by_page(search_results_plus_one) 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 c0a64d8d4391..7d9d0964a9f9 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 @@ -1,25 +1,32 @@ defmodule BlockScoutWeb.SmartContractController do use BlockScoutWeb, :controller + alias BlockScoutWeb.AddressView alias Explorer.Chain + alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{Reader, Writer} + import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1] + @burn_address "0x0000000000000000000000000000000000000000" - def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action}) do + def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do address_options = [ necessity_by_association: %{ :smart_contract => :optional } ] + is_custom_abi = parse_boolean(params["is_custom_abi"]) + with true <- ajax?(conn), + {:custom_abi, false} <- {:custom_abi, is_custom_abi}, {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do implementation_address_hash_string = if contract_type == "proxy" do - address.hash - |> Chain.get_implementation_address_hash(address.smart_contract.abi) + address.smart_contract + |> SmartContract.get_implementation_address_hash() |> Tuple.to_list() |> List.first() || @burn_address else @@ -78,6 +85,9 @@ defmodule BlockScoutWeb.SmartContractController do action: action ) else + {:custom_abi, true} -> + custom_abi_render(conn, params) + :error -> unprocessable_entity(conn) @@ -91,6 +101,51 @@ defmodule BlockScoutWeb.SmartContractController do def index(conn, _), do: not_found(conn) + defp custom_abi_render(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action}) do + with custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string), + false <- is_nil(custom_abi), + abi <- custom_abi.abi, + {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do + functions = + if action == "write" do + Writer.filter_write_functions(abi) + else + Reader.read_only_functions_from_abi(abi, address_hash) + end + + read_functions_required_wallet = + if action == "read" do + Reader.read_functions_required_wallet_from_abi(abi) + else + [] + end + + contract_abi = Poison.encode!(abi) + + conn + |> put_status(200) + |> put_layout(false) + |> render( + "_functions.html", + read_functions_required_wallet: read_functions_required_wallet, + read_only_functions: functions, + address: %{hash: address_hash}, + custom_abi: true, + contract_abi: contract_abi, + implementation_address: @burn_address, + implementation_abi: [], + contract_type: contract_type, + action: action + ) + else + :error -> + unprocessable_entity(conn) + + _ -> + not_found(conn) + end + end + def show(conn, params) do address_options = [ necessity_by_association: %{ @@ -102,31 +157,40 @@ defmodule BlockScoutWeb.SmartContractController do } ] + custom_abi = + if parse_boolean(params["is_custom_abi"]), do: AddressView.fetch_custom_abi(conn, params["id"]), else: nil + 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 params["type"] == "proxy", do: :proxy, else: :regular - {args_count, _} = Integer.parse(params["args_count"]) - args = - if args_count < 1, - do: [], - else: for(x <- 0..(args_count - 1), do: params["arg_" <> to_string(x)] |> convert_map_to_array()) + if is_nil(params["args_count"]) do + # we should convert: %{"0" => _, "1" => _} to [_, _] + params["args"] |> convert_map_to_array() + else + {args_count, _} = Integer.parse(params["args_count"]) + + if args_count < 1, + do: [], + else: for(x <- 0..(args_count - 1), do: params["arg_" <> to_string(x)] |> convert_map_to_array()) + end %{output: outputs, names: names} = - if params["from"] do - Reader.query_function_with_names( + if custom_abi do + Reader.query_function_with_names_custom_abi( address_hash, %{method_id: params["method_id"], args: args}, - contract_type, - params["from"] + params["from"], + custom_abi.abi ) else Reader.query_function_with_names( address_hash, %{method_id: params["method_id"], args: args}, - contract_type + contract_type, + params["from"] ) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex index e16d9bf4707d..9a6bee2686b2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex @@ -1,6 +1,9 @@ defmodule BlockScoutWeb.Tokens.ContractController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{AccessHelpers, TabHelpers} alias Explorer.{Chain, Market} alias Explorer.Chain.Address @@ -33,7 +36,8 @@ defmodule BlockScoutWeb.Tokens.ContractController do type: type, action: action, token: Market.add_price(token), - counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> 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 806badc1958e..710ff74e7539 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 @@ -1,6 +1,9 @@ defmodule BlockScoutWeb.Tokens.HolderController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{AccessHelpers, Controller} alias BlockScoutWeb.Tokens.HolderView alias Explorer.{Chain, Market} @@ -66,7 +69,8 @@ defmodule BlockScoutWeb.Tokens.HolderController do "index.html", current_path: Controller.current_full_path(conn), token: Market.add_price(token), - counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}) + counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex index 0d97c56692e8..3ee291f43b45 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex @@ -1,6 +1,7 @@ defmodule BlockScoutWeb.Tokens.Instance.HolderController do use BlockScoutWeb, :controller + alias BlockScoutWeb.Controller alias BlockScoutWeb.Tokens.HolderView alias Explorer.{Chain, Market} alias Explorer.Chain.Address @@ -55,13 +56,13 @@ defmodule BlockScoutWeb.Tokens.Instance.HolderController do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash, options), - {:ok, token_transfer} <- - Chain.erc721_token_instance_from_token_id_and_token_address(token_id, hash) do + {:ok, token_instance} <- + Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do render( conn, "index.html", - token_instance: token_transfer, - current_path: current_path(conn), + token_instance: %{instance: token_instance, token_id: Decimal.new(token_id)}, + current_path: Controller.current_full_path(conn), token: Market.add_price(token), total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex index e075e86cff9a..b404cc91b81e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex @@ -9,13 +9,13 @@ defmodule BlockScoutWeb.Tokens.Instance.MetadataController do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash, options), - {:ok, token_transfer} <- - Chain.erc721_token_instance_from_token_id_and_token_address(token_id, hash) do - if token_transfer.instance && token_transfer.instance.metadata do + {:ok, token_instance} <- + Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do + if token_instance.metadata do render( conn, "index.html", - token_instance: token_transfer, + token_instance: %{instance: token_instance, token_id: Decimal.new(token_id)}, current_path: Controller.current_full_path(conn), token: Market.add_price(token), total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex index 7350c318818f..1e92527d0ffd 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex @@ -58,12 +58,12 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash, options), - {:ok, token_transfer} <- - Chain.erc721_token_instance_from_token_id_and_token_address(token_id, hash) do + {:ok, token_instance} <- + Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do render( conn, "index.html", - token_instance: token_transfer, + token_instance: %{instance: token_instance, token_id: Decimal.new(token_id)}, current_path: Controller.current_full_path(conn), token: Market.add_price(token), total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex index 1696ac13394b..d7d0a4d8c460 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex @@ -7,7 +7,7 @@ defmodule BlockScoutWeb.Tokens.InstanceController do def show(conn, %{"token_id" => token_address_hash, "id" => token_id}) do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), :ok <- Chain.check_token_exists(hash), - :ok <- Chain.check_erc721_token_instance_exists(token_id, hash) do + :ok <- Chain.check_erc721_or_erc1155_token_instance_exists(token_id, hash) do token_instance_transfer_path = conn |> token_instance_transfer_path(:index, token_address_hash, token_id) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex index 5129856a183a..d9cb630ca438 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do alias BlockScoutWeb.AccessHelpers alias BlockScoutWeb.Tokens.{HolderController, InventoryView} alias Explorer.Chain - alias Explorer.Chain.TokenTransfer + alias Explorer.Chain.Token.Instance alias Phoenix.View import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0] @@ -13,16 +13,16 @@ defmodule BlockScoutWeb.Tokens.InventoryController do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, token} <- Chain.token_from_address_hash(address_hash), {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do - unique_tokens = + unique_token_instances = Chain.address_to_unique_tokens( token.contract_address_hash, unique_tokens_paging_options(params) ) - {unique_tokens_paginated, next_page} = split_list_by_page(unique_tokens) + {unique_token_instances_paginated, next_page} = split_list_by_page(unique_token_instances) next_page_path = - case unique_tokens_next_page(next_page, unique_tokens_paginated, params) do + case unique_tokens_next_page(next_page, unique_token_instances_paginated, params) do nil -> nil @@ -36,12 +36,12 @@ defmodule BlockScoutWeb.Tokens.InventoryController do end items = - unique_tokens_paginated - |> Enum.map(fn token_transfer -> + unique_token_instances_paginated + |> Enum.map(fn instance -> View.render_to_string( InventoryView, "_token.html", - token_transfer: token_transfer, + instance: instance, token: token, conn: conn ) @@ -81,7 +81,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do Map.merge(params, paging_params(List.last(list))) end - defp paging_params(%TokenTransfer{token_id: token_id}) do + defp paging_params(%Instance{token_id: token_id}) do %{"unique_token" => Decimal.to_integer(token_id)} end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/token_controller.ex index 6a1ea07b1730..73739141e7dd 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/token_controller.ex @@ -5,7 +5,6 @@ defmodule BlockScoutWeb.Tokens.TokenController do alias BlockScoutWeb.AccessHelpers alias Explorer.Chain - alias Explorer.Counters.{TokenHoldersCounter, TokenTransfersCounter} def show(conn, %{"id" => address_hash_string}) do redirect(conn, to: AccessHelpers.get_path(conn, :token_transfer_path, :index, address_hash_string)) @@ -14,7 +13,7 @@ defmodule BlockScoutWeb.Tokens.TokenController do def token_counters(conn, %{"id" => address_hash_string}) do case Chain.string_to_address_hash(address_hash_string) do {:ok, address_hash} -> - {transfer_count, token_holder_count} = fetch_token_counters(address_hash, 30_000) + {transfer_count, token_holder_count} = Chain.fetch_token_counters(address_hash, 30_000) json(conn, %{transfer_count: transfer_count, token_holder_count: token_holder_count}) @@ -22,34 +21,4 @@ defmodule BlockScoutWeb.Tokens.TokenController do not_found(conn) end end - - defp fetch_token_counters(address_hash, timeout) do - total_token_transfers_task = - Task.async(fn -> - TokenTransfersCounter.fetch(address_hash) - end) - - total_token_holders_task = - Task.async(fn -> - TokenHoldersCounter.fetch(address_hash) - end) - - [total_token_transfers_task, total_token_holders_task] - |> Task.yield_many(timeout) - |> Enum.map(fn {_task, res} -> - case res do - {:ok, result} -> - result - - {:exit, reason} -> - Logger.warn("Query fetching token counters terminated: #{inspect(reason)}") - 0 - - nil -> - Logger.warn("Query fetching token counters timed out.") - 0 - end - end) - |> List.to_tuple() - end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex index 9c3ce0278ed2..ad7db933c00e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex @@ -1,6 +1,9 @@ defmodule BlockScoutWeb.Tokens.TransferController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + alias BlockScoutWeb.{AccessHelpers, Controller} alias BlockScoutWeb.Tokens.TransferView alias Explorer.{Chain, Market} @@ -71,7 +74,8 @@ defmodule BlockScoutWeb.Tokens.TransferController do counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}), current_path: Controller.current_full_path(conn), token: Market.add_price(token), - token_total_supply_status: TokenTotalSupplyOnDemand.trigger_fetch(address_hash) + token_total_supply_status: TokenTotalSupplyOnDemand.trigger_fetch(address_hash), + tags: get_address_tags(address_hash, current_user(conn)) ) else {:restricted_access, _} -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex index 68029fe40ca9..83818de3b5fd 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex @@ -1,6 +1,8 @@ defmodule BlockScoutWeb.TransactionController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Chain, only: [ fetch_page_number: 1, @@ -10,6 +12,9 @@ defmodule BlockScoutWeb.TransactionController do split_list_by_page: 1 ] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2] + alias BlockScoutWeb.{ AccessHelpers, Controller, @@ -158,16 +163,24 @@ defmodule BlockScoutWeb.TransactionController do "show_token_transfers.html", exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), block_height: Chain.block_height(), - current_path: current_path(conn), + current_path: Controller.current_full_path(conn), + current_user: current_user(conn), show_token_transfers: true, - transaction: transaction + transaction: transaction, + from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), + to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), + tx_tags: + get_transaction_with_addresses_tags( + transaction, + current_user(conn) + ) ) else :not_found -> set_not_found_view(conn, id) :error -> - set_invalid_view(conn, id) + unprocessable_entity(conn) {:error, :not_found} -> set_not_found_view(conn, id) @@ -187,17 +200,25 @@ defmodule BlockScoutWeb.TransactionController do conn, "show_internal_transactions.html", exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - current_path: current_path(conn), + current_path: Controller.current_full_path(conn), + current_user: current_user(conn), block_height: Chain.block_height(), show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash), - transaction: transaction + transaction: transaction, + from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), + to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), + tx_tags: + get_transaction_with_addresses_tags( + transaction, + current_user(conn) + ) ) else :not_found -> set_not_found_view(conn, id) :error -> - set_invalid_view(conn, id) + unprocessable_entity(conn) {:error, :not_found} -> set_not_found_view(conn, id) @@ -208,7 +229,7 @@ defmodule BlockScoutWeb.TransactionController do end else :error -> - set_invalid_view(conn, id) + unprocessable_entity(conn) :not_found -> set_not_found_view(conn, id) @@ -221,11 +242,4 @@ defmodule BlockScoutWeb.TransactionController do |> put_view(TransactionView) |> render("not_found.html", transaction_hash: transaction_hash_string) end - - def set_invalid_view(conn, transaction_hash_string) do - conn - |> put_status(422) - |> put_view(TransactionView) - |> render("invalid.html", transaction_hash: transaction_hash_string) - end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex index 61b7122180a7..155e95d2d714 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -1,7 +1,10 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2] alias BlockScoutWeb.{AccessHelpers, Controller, InternalTransactionView, TransactionController} alias Explorer.{Chain, Market} @@ -70,7 +73,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do TransactionController.set_not_found_view(conn, transaction_hash_string) :error -> - TransactionController.set_invalid_view(conn, transaction_hash_string) + unprocessable_entity(conn) {:error, :not_found} -> TransactionController.set_not_found_view(conn, transaction_hash_string) @@ -101,16 +104,24 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do "index.html", exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), current_path: Controller.current_full_path(conn), + current_user: current_user(conn), block_height: Chain.block_height(), show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash), - transaction: transaction + transaction: transaction, + from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), + to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), + tx_tags: + get_transaction_with_addresses_tags( + transaction, + current_user(conn) + ) ) else {:restricted_access, _} -> TransactionController.set_not_found_view(conn, transaction_hash_string) :error -> - TransactionController.set_invalid_view(conn, transaction_hash_string) + unprocessable_entity(conn) {:error, :not_found} -> TransactionController.set_not_found_view(conn, transaction_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex index f9abbf207b78..ed44e98184e1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex @@ -1,7 +1,10 @@ defmodule BlockScoutWeb.TransactionLogController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2] alias BlockScoutWeb.{AccessHelpers, Controller, TransactionController, TransactionLogView} alias Explorer.{Chain, Market} @@ -66,7 +69,7 @@ defmodule BlockScoutWeb.TransactionLogController do TransactionController.set_not_found_view(conn, transaction_hash_string) :error -> - TransactionController.set_invalid_view(conn, transaction_hash_string) + unprocessable_entity(conn) {:error, :not_found} -> TransactionController.set_not_found_view(conn, transaction_hash_string) @@ -95,15 +98,23 @@ defmodule BlockScoutWeb.TransactionLogController do block_height: Chain.block_height(), show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash), current_path: Controller.current_full_path(conn), + current_user: current_user(conn), transaction: transaction, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), + to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), + tx_tags: + get_transaction_with_addresses_tags( + transaction, + current_user(conn) + ) ) else {:restricted_access, _} -> TransactionController.set_not_found_view(conn, transaction_hash_string) :error -> - TransactionController.set_invalid_view(conn, transaction_hash_string) + unprocessable_entity(conn) {:error, :not_found} -> TransactionController.set_not_found_view(conn, transaction_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex index 44584b795f30..fc83d0fa1646 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex @@ -1,6 +1,10 @@ defmodule BlockScoutWeb.TransactionRawTraceController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2] + alias BlockScoutWeb.{AccessHelpers, TransactionController} alias EthereumJSONRPC alias Explorer.{Chain, Market} @@ -78,7 +82,7 @@ defmodule BlockScoutWeb.TransactionRawTraceController do TransactionController.set_not_found_view(conn, hash_string) :error -> - TransactionController.set_invalid_view(conn, hash_string) + unprocessable_entity(conn) {:error, :not_found} -> TransactionController.set_not_found_view(conn, hash_string) @@ -92,8 +96,16 @@ defmodule BlockScoutWeb.TransactionRawTraceController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), internal_transactions: internal_transactions, block_height: Chain.block_height(), + current_user: current_user(conn), show_token_transfers: Chain.transaction_has_token_transfers?(hash), - transaction: transaction + transaction: transaction, + from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), + to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), + tx_tags: + get_transaction_with_addresses_tags( + transaction, + current_user(conn) + ) ) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex new file mode 100644 index 000000000000..6bf8c86fd485 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex @@ -0,0 +1,465 @@ +defmodule BlockScoutWeb.TransactionStateController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.{ + AccessHelpers, + Controller, + TransactionController, + TransactionStateView + } + + alias Explorer.{Chain, Chain.Wei, Market, PagingOptions} + alias Explorer.ExchangeRates.Token + alias Phoenix.View + alias Indexer.Fetcher.{CoinBalance, TokenBalance} + + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2] + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + + @burn_address_hash burn_address_hash + + def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do + with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string), + :ok <- Chain.check_transaction_exists(transaction_hash), + {:ok, transaction} <- + Chain.hash_to_transaction( + transaction_hash, + necessity_by_association: %{ + [block: :miner] => :required, + from_address: :required, + to_address: :optional + } + ), + {:ok, false} <- + AccessHelpers.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- + AccessHelpers.restricted_access?(to_string(transaction.to_address_hash), params) do + full_options = [ + necessity_by_association: %{ + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + from_address: :required, + to_address: :required + }, + # we need to consider all token transfers in block to show whole state change of transaction + paging_options: %PagingOptions{key: nil, page_size: nil} + ] + + token_transfers = Chain.transaction_to_token_transfers(transaction_hash, full_options) + + block = transaction.block + + block_txs = + Chain.block_to_transactions(block.hash, + necessity_by_association: %{}, + paging_options: %PagingOptions{key: nil, page_size: nil} + ) + + {from_before, to_before, miner_before} = coin_balances_before(transaction, block_txs) + + from_hash = transaction.from_address_hash + to_hash = transaction.to_address_hash + miner_hash = block.miner_hash + + from_coin_entry = + if from_hash not in [to_hash, miner_hash] do + from = transaction.from_address + from_after = do_update_coin_balance_from_tx(from_hash, transaction, from_before, block) + + View.render_to_string( + TransactionStateView, + "_state_change.html", + coin_or_token_transfers: :coin, + address: from, + burn_address_hash: @burn_address_hash, + balance_before: from_before, + balance_after: from_after, + balance_diff: Wei.sub(from_after, from_before), + conn: conn + ) + end + + to_coin_entry = + if not is_nil(to_hash) and to_hash != miner_hash do + to = transaction.to_address + to_after = do_update_coin_balance_from_tx(to_hash, transaction, to_before, block) + + View.render_to_string( + TransactionStateView, + "_state_change.html", + coin_or_token_transfers: :coin, + address: to, + burn_address_hash: @burn_address_hash, + balance_before: to_before, + balance_after: to_after, + balance_diff: Wei.sub(to_after, to_before), + conn: conn + ) + end + + miner = block.miner + miner_after = do_update_coin_balance_from_tx(miner_hash, transaction, miner_before, block) + + miner_entry = + View.render_to_string( + TransactionStateView, + "_state_change.html", + coin_or_token_transfers: :coin, + address: miner, + burn_address_hash: @burn_address_hash, + balance_before: miner_before, + balance_after: miner_after, + balance_diff: Wei.sub(miner_after, miner_before), + miner: true, + conn: conn + ) + + token_balances_before = token_balances_before(token_transfers, transaction, block_txs) + + token_balances_after = + do_update_token_balances_from_token_transfers( + token_transfers, + token_balances_before, + :include_transfers + ) + + items = + for {address, balances} <- token_balances_after, + {token_hash, {balance, transfers}} <- balances do + balance_before = token_balances_before[address][token_hash] + + View.render_to_string( + TransactionStateView, + "_state_change.html", + coin_or_token_transfers: transfers, + address: address, + burn_address_hash: @burn_address_hash, + balance_before: balance_before, + balance_after: balance, + balance_diff: Decimal.sub(balance, balance_before), + conn: conn + ) + end + + json(conn, %{ + items: [from_coin_entry, to_coin_entry, miner_entry | items] |> Enum.reject(&is_nil/1) |> Enum.sort() + }) + else + {:restricted_access, _} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + :not_found -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + end + end + + def index(conn, %{"transaction_id" => transaction_hash_string} = params) do + with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string), + {:ok, transaction} <- + Chain.hash_to_transaction( + transaction_hash, + necessity_by_association: %{ + :block => :optional, + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [to_address: :smart_contract] => :optional, + :token_transfers => :optional + } + ), + {:ok, false} <- + AccessHelpers.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- + AccessHelpers.restricted_access?(to_string(transaction.to_address_hash), params) do + render( + conn, + "index.html", + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + block_height: Chain.block_height(), + current_path: Controller.current_full_path(conn), + show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash), + transaction: transaction, + from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), + to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), + tx_tags: + get_transaction_with_addresses_tags( + transaction, + current_user(conn) + ), + current_user: current_user(conn) + ) + else + :not_found -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + {:restricted_access, _} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + end + end + + def coin_balance(address_hash, _block_number) when is_nil(address_hash) do + %Wei{value: Decimal.new(0)} + end + + def coin_balance(address_hash, block_number) do + case Chain.get_coin_balance(address_hash, block_number) do + %{value: val} when not is_nil(val) -> + val + + _ -> + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + CoinBalance.run([{address_hash.bytes, block_number}], json_rpc_named_arguments) + # after CoinBalance.run balance is fetched and imported, so we can call coin_balance again + coin_balance(address_hash, block_number) + end + end + + def coin_balances_before(tx, block_txs) do + block = tx.block + + from_before = coin_balance(tx.from_address_hash, block.number - 1) + to_before = coin_balance(tx.to_address_hash, block.number - 1) + miner_before = coin_balance(block.miner_hash, block.number - 1) + + block_txs + |> Enum.reduce_while( + {from_before, to_before, miner_before}, + fn block_tx, {block_from, block_to, block_miner} = state -> + if block_tx.index < tx.index do + {:cont, + {do_update_coin_balance_from_tx(tx.from_address_hash, block_tx, block_from, block), + do_update_coin_balance_from_tx(tx.to_address_hash, block_tx, block_to, block), + do_update_coin_balance_from_tx(tx.block.miner_hash, block_tx, block_miner, block)}} + else + # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx + {:halt, state} + end + end + ) + end + + defp do_update_coin_balance_from_tx(address_hash, tx, balance, block) do + from = tx.from_address_hash + to = tx.to_address_hash + miner = block.miner_hash + + balance + |> (&if(address_hash == from, do: Wei.sub(&1, from_loss(tx)), else: &1)).() + |> (&if(address_hash == to, do: Wei.sum(&1, to_profit(tx)), else: &1)).() + |> (&if(address_hash == miner, do: Wei.sum(&1, miner_profit(tx, block)), else: &1)).() + end + + def token_balance(@burn_address_hash, _token_transfer, _block_number) do + Decimal.new(0) + end + + def token_balance(address_hash, token_transfer, block_number) do + token = token_transfer.token + token_contract_address_hash = token.contract_address_hash + + case Chain.get_token_balance(address_hash, token_contract_address_hash, block_number) do + %{value: val} when not is_nil(val) -> + val + + # we haven't fetched this balance yet + _ -> + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + token_id_int = + case token_transfer.token_id do + %Decimal{} -> Decimal.to_integer(token_transfer.token_id) + id_int when is_integer(id_int) -> id_int + _ -> token_transfer.token_id + end + + TokenBalance.run( + [ + {address_hash.bytes, token_contract_address_hash.bytes, block_number, token.type, token_id_int, 0} + ], + json_rpc_named_arguments + ) + + # after TokenBalance.run balance is fetched and imported, so we can call token_balance again + token_balance(address_hash, token_transfer, block_number) + end + end + + def token_balances_before(token_transfers, tx, block_txs) do + balances_before = + token_transfers + |> Enum.reduce(%{}, fn transfer, balances_map -> + from = transfer.from_address + to = transfer.to_address + token_hash = transfer.token_contract_address_hash + prev_block = transfer.block_number - 1 + + balances_with_from = + case balances_map do + # from address already in the map + %{^from => %{^token_hash => _}} -> + balances_map + + # we need to add from address into the map + _ -> + put_in( + balances_map, + Enum.map([from, token_hash], &Access.key(&1, %{})), + token_balance(from.hash, transfer, prev_block) + ) + end + + case balances_with_from do + # to address already in the map + %{^to => %{^token_hash => _}} -> + balances_with_from + + # we need to add to address into the map + _ -> + put_in( + balances_with_from, + Enum.map([to, token_hash], &Access.key(&1, %{})), + token_balance(to.hash, transfer, prev_block) + ) + end + end) + + block_txs + |> Enum.reduce_while( + balances_before, + fn block_tx, state -> + if block_tx.index < tx.index do + {:cont, do_update_token_balances_from_token_transfers(block_tx.token_transfers, state)} + else + # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx + {:halt, state} + end + end + ) + end + + defp do_update_token_balances_from_token_transfers( + token_transfers, + balances_map, + include_transfers \\ :no + ) do + Enum.reduce( + token_transfers, + balances_map, + &token_transfers_balances_reducer(&1, &2, include_transfers) + ) + end + + defp token_transfers_balances_reducer(transfer, state_balances_map, include_transfers) do + from = transfer.from_address + to = transfer.to_address + token = transfer.token_contract_address_hash + + balances_map_from_included = + case state_balances_map do + # from address is needed to be updated in our map + %{^from => %{^token => val}} -> + put_in( + state_balances_map, + Enum.map([from, token], &Access.key(&1, %{})), + do_update_balance(val, :from, transfer, include_transfers) + ) + + # we are not interested in this address + _ -> + state_balances_map + end + + case balances_map_from_included do + # to address is needed to be updated in our map + %{^to => %{^token => val}} -> + put_in( + balances_map_from_included, + Enum.map([to, token], &Access.key(&1, %{})), + do_update_balance(val, :to, transfer, include_transfers) + ) + + # we are not interested in this address + _ -> + balances_map_from_included + end + end + + # point of this function is to include all transfers for frontend if option :include_transfer is passed + defp do_update_balance(old_val, type, transfer, include_transfers) do + transfer_amount = if is_nil(transfer.amount), do: 1, else: transfer.amount + + case {include_transfers, old_val, type} do + {:include_transfers, {val, transfers}, :from} -> + {Decimal.sub(val, transfer_amount), [{type, transfer} | transfers]} + + {:include_transfers, {val, transfers}, :to} -> + {Decimal.add(val, transfer_amount), [{type, transfer} | transfers]} + + {:include_transfers, val, :from} -> + {Decimal.sub(val, transfer_amount), [{type, transfer}]} + + {:include_transfers, val, :to} -> + {Decimal.add(val, transfer_amount), [{type, transfer}]} + + {_, val, :from} -> + Decimal.sub(val, transfer_amount) + + {_, val, :to} -> + Decimal.add(val, transfer_amount) + end + end + + def from_loss(tx) do + {_, fee} = Chain.fee(tx, :wei) + + if error?(tx) do + %Wei{value: fee} + else + Wei.sum(tx.value, %Wei{value: fee}) + end + end + + def to_profit(tx) do + if error?(tx) do + %Wei{value: 0} + else + tx.value + end + end + + def miner_profit(tx, block) do + base_fee_per_gas = block.base_fee_per_gas || %Wei{value: Decimal.new(0)} + max_priority_fee_per_gas = tx.max_priority_fee_per_gas || tx.gas_price + max_fee_per_gas = tx.max_fee_per_gas || tx.gas_price + + priority_fee_per_gas = + Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x -> + Wei.to(x, :wei) + end) + + Wei.mult(priority_fee_per_gas, tx.gas_used) + end + + defp error?(tx) do + case Chain.transaction_to_status(tx) do + {:error, _} -> true + _ -> false + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex index 95e5b34d0a40..d391ed5d13e9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex @@ -1,7 +1,10 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do use BlockScoutWeb, :controller + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] + import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2] alias BlockScoutWeb.{AccessHelpers, Controller, TransactionController, TransactionTokenTransferView} alias Explorer.{Chain, Market} @@ -30,8 +33,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do [from_address: :names] => :optional, [to_address: :names] => :optional, from_address: :required, - to_address: :required, - token: :required + to_address: :required } ], paging_options(params) @@ -74,7 +76,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do TransactionController.set_not_found_view(conn, transaction_hash_string) :error -> - TransactionController.set_invalid_view(conn, transaction_hash_string) + unprocessable_entity(conn) {:error, :not_found} -> TransactionController.set_not_found_view(conn, transaction_hash_string) @@ -106,15 +108,23 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), block_height: Chain.block_height(), current_path: Controller.current_full_path(conn), + current_user: current_user(conn), show_token_transfers: true, - transaction: transaction + transaction: transaction, + from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), + to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), + tx_tags: + get_transaction_with_addresses_tags( + transaction, + current_user(conn) + ) ) else :not_found -> TransactionController.set_not_found_view(conn, transaction_hash_string) :error -> - TransactionController.set_invalid_view(conn, transaction_hash_string) + unprocessable_entity(conn) {:error, :not_found} -> TransactionController.set_not_found_view(conn, transaction_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex new file mode 100644 index 000000000000..c75b25e27ecb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex @@ -0,0 +1,75 @@ +defmodule BlockScoutWeb.VerifiedContractsController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1, fetch_page_number: 1] + + alias BlockScoutWeb.{Controller, VerifiedContractsView} + alias Explorer.{Chain, Market} + alias Explorer.ExchangeRates.Token + alias Phoenix.View + + @necessity_by_association %{[address: :token] => :optional} + + def index(conn, %{"type" => "JSON"} = params) do + full_options = + [necessity_by_association: @necessity_by_association] + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(current_filter(params)) + |> Keyword.merge(search_query(params)) + + verified_contracts_plus_one = Chain.verified_contracts(full_options) + {verified_contracts, next_page} = split_list_by_page(verified_contracts_plus_one) + + items = + for contract <- verified_contracts do + token = + if contract.address.token, + do: Market.get_exchange_rate(contract.address.token.symbol), + else: Token.null() + + View.render_to_string(VerifiedContractsView, "_contract.html", + contract: contract, + token: token + ) + end + + next_page_path = + case next_page_params(next_page, verified_contracts, params) do + nil -> nil + next_page_params -> verified_contracts_path(conn, :index, Map.delete(next_page_params, "type")) + end + + json(conn, %{items: items, next_page_path: next_page_path}) + end + + def index(conn, params) do + render(conn, "index.html", + current_path: Controller.current_full_path(conn), + filter: params["filter"], + page_number: params |> fetch_page_number() |> Integer.to_string(), + contracts_count: Chain.count_contracts_from_cache(), + verified_contracts_count: Chain.count_verified_contracts_from_cache(), + new_contracts_count: Chain.count_new_contracts_from_cache(), + new_verified_contracts_count: Chain.count_new_verified_contracts_from_cache() + ) + end + + defp current_filter(%{"filter" => "solidity"}) do + [filter: :solidity] + end + + defp current_filter(%{"filter" => "vyper"}) do + [filter: :vyper] + end + + defp current_filter(_), do: [] + + defp search_query(%{"search" => ""}), do: [] + + defp search_query(%{"search" => search_string}) do + [search: search_string] + end + + defp search_query(_), do: [] +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/visualize_sol2uml_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/visualize_sol2uml_controller.ex new file mode 100644 index 000000000000..3863fb31a8c6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/visualize_sol2uml_controller.ex @@ -0,0 +1,67 @@ +defmodule BlockScoutWeb.VisualizeSol2umlController do + use BlockScoutWeb, :controller + alias Explorer.Chain + alias Explorer.Visualize.Sol2uml + + def index(conn, %{"type" => "JSON", "address" => address_hash_string}) do + address_options = [ + necessity_by_association: %{ + :smart_contract => :optional + } + ] + + if Sol2uml.enabled?() do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true), + # check that contract is verified. partial and twin verification is ok for this case + false <- is_nil(address.smart_contract) do + sources = + address.smart_contract_additional_sources + |> Enum.map(fn additional_source -> {additional_source.file_name, additional_source.contract_source_code} end) + |> Enum.into(%{}) + |> Map.merge(%{ + get_contract_filename(address.smart_contract.file_path) => address.smart_contract.contract_source_code + }) + + params = %{ + sources: sources + } + + case Sol2uml.visualize_contracts(params) do + {:ok, svg} -> json(conn, %{"address" => address.hash, "contract_svg" => svg, "error" => nil}) + {:error, error} -> json(conn, %{"address" => address.hash, "contract_svg" => nil, "error" => error}) + end + else + _ -> json(conn, %{error: "contract not found or unverified"}) + end + else + not_found(conn) + end + end + + def index(conn, %{"address" => address_hash_string}) do + address_options = [ + necessity_by_association: %{ + :smart_contract => :optional + } + ] + + with true <- Sol2uml.enabled?(), + {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do + render(conn, "index.html", + address: address, + get_svg_path: visualize_sol2uml_path(conn, :index, %{"type" => "JSON", "address" => address_hash_string}) + ) + else + _ -> not_found(conn) + end + end + + def index(conn, _) do + not_found(conn) + end + + def get_contract_filename(nil), do: "main.sol" + def get_contract_filename(filename), do: filename +end diff --git a/apps/block_scout_web/lib/block_scout_web/counters/blocks_indexed_counter.ex b/apps/block_scout_web/lib/block_scout_web/counters/blocks_indexed_counter.ex index 3eede51f907d..8c4ed182102d 100644 --- a/apps/block_scout_web/lib/block_scout_web/counters/blocks_indexed_counter.ex +++ b/apps/block_scout_web/lib/block_scout_web/counters/blocks_indexed_counter.ex @@ -13,7 +13,7 @@ defmodule BlockScoutWeb.Counters.BlocksIndexedCounter do # It is undesirable to automatically start the counter in all environments. # Consider the test environment: if it initiates but does not finish before a # test ends, that test will fail. - config = Application.get_env(:block_scout_web, __MODULE__) + config = Application.compile_env(:block_scout_web, __MODULE__) @enabled Keyword.get(config, :enabled) @doc """ @@ -36,15 +36,11 @@ defmodule BlockScoutWeb.Counters.BlocksIndexedCounter do end def calculate_blocks_indexed do - ratio = Chain.indexed_ratio() + indexed_ratio_blocks = Chain.indexed_ratio_blocks() - finished? = - case Decimal.compare(ratio, 1) do - :lt -> false - _ -> Chain.finished_indexing?() - end + finished? = Chain.finished_indexing?(indexed_ratio_blocks) - Notifier.broadcast_blocks_indexed_ratio(ratio, finished?) + Notifier.broadcast_blocks_indexed_ratio(indexed_ratio_blocks, finished?) end defp schedule_next_consolidation do diff --git a/apps/block_scout_web/lib/block_scout_web/counters/internal_transactions_indexed_counter.ex b/apps/block_scout_web/lib/block_scout_web/counters/internal_transactions_indexed_counter.ex new file mode 100644 index 000000000000..76085fe152e2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/counters/internal_transactions_indexed_counter.ex @@ -0,0 +1,58 @@ +defmodule BlockScoutWeb.Counters.InternalTransactionsIndexedCounter do + @moduledoc """ + Module responsible for fetching and consolidating the number pending block operations (internal transactions) indexed. + + It loads the count asynchronously in a time interval. + """ + + use GenServer + + alias BlockScoutWeb.Notifier + alias Explorer.Chain + + # It is undesirable to automatically start the counter in all environments. + # Consider the test environment: if it initiates but does not finish before a + # test ends, that test will fail. + config = Application.compile_env(:block_scout_web, __MODULE__) + @enabled Keyword.get(config, :enabled) + + @doc """ + Starts a process to periodically update the % of internal transactions indexed. + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(args) do + if @enabled do + Task.start_link(&calculate_internal_transactions_indexed/0) + + schedule_next_consolidation() + end + + {:ok, args} + end + + def calculate_internal_transactions_indexed do + indexed_ratio_internal_transactions = Chain.indexed_ratio_internal_transactions() + + finished? = Chain.finished_indexing?(indexed_ratio_internal_transactions) + + Notifier.broadcast_internal_transactions_indexed_ratio(indexed_ratio_internal_transactions, finished?) + end + + defp schedule_next_consolidation do + Process.send_after(self(), :calculate_internal_transactions_indexed, :timer.minutes(7)) + end + + @impl true + def handle_info(:calculate_internal_transactions_indexed, state) do + calculate_internal_transactions_indexed() + + schedule_next_consolidation() + + {:noreply, state} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/endpoint.ex b/apps/block_scout_web/lib/block_scout_web/endpoint.ex index f53445bc052f..aaedee900067 100644 --- a/apps/block_scout_web/lib/block_scout_web/endpoint.ex +++ b/apps/block_scout_web/lib/block_scout_web/endpoint.ex @@ -2,11 +2,12 @@ defmodule BlockScoutWeb.Endpoint do use Phoenix.Endpoint, otp_app: :block_scout_web use Absinthe.Phoenix.Endpoint - if Application.get_env(:block_scout_web, :sql_sandbox) do + if Application.compile_env(:block_scout_web, :sql_sandbox) do plug(Phoenix.Ecto.SQL.Sandbox, repo: Explorer.Repo) end socket("/socket", BlockScoutWeb.UserSocket, websocket: [timeout: 45_000]) + socket("/socket/v2", BlockScoutWeb.UserSocketV2, websocket: [timeout: 45_000]) # Serve at "/" the static files from "priv/static" directory. # @@ -59,11 +60,15 @@ defmodule BlockScoutWeb.Endpoint do # The session will be stored in the cookie and signed, # this means its contents can be read but not tampered with. # Set :encryption_salt if you would also like to encrypt it. + plug( Plug.Session, - store: :cookie, + store: BlockScoutWeb.Plug.RedisCookie, key: "_explorer_key", - signing_salt: "iC2ksJHS" + signing_salt: "iC2ksJHS", + same_site: "Lax", + http_only: false, + domain: Application.compile_env(:block_scout_web, :cookie_domain) ) use SpandexPhoenix 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 176627492972..3b876b4f751b 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -106,7 +106,8 @@ defmodule BlockScoutWeb.Etherscan do "transactionHash" => "0xd65b788c610949704a5f9aac2228c7c777434dfe11c863a12306f57fcbd8cdbb", "index" => "0", "input" => "", - "type" => "create", + "type" => "call", + "callType" => "delegatecall", "gas" => "814937", "gasUsed" => "536262", "isError" => "0", @@ -676,6 +677,7 @@ defmodule BlockScoutWeb.Etherscan do } @token_id_type %{ + name: "Token ID", type: "integer", definition: "id of token", example: ~s("0") @@ -806,6 +808,11 @@ defmodule BlockScoutWeb.Etherscan do definition: ~s(Possible values: "create", "call", "reward", or "selfdestruct"), example: ~s("create") }, + callType: %{ + type: "type", + definition: ~s(Possible values: "call", "callcode", "delegatecall", or "staticcall"), + example: ~s("delegatecall") + }, gas: @gas_type, gasUsed: @gas_type, isError: %{ @@ -861,8 +868,21 @@ defmodule BlockScoutWeb.Etherscan do definition: "The transferred amount.", example: ~s("663046792267785498951364") }, + values: %{ + type: "array", + array_type: %{ + name: "Transferred amount", + type: "integer", + definition: "The transferred amount of particular token instance." + }, + definition: "Transferred amounts of token instances in ERC-1155 batch transfer corresponding to tokenIDs field." + }, tokenName: @token_name_type, tokenID: @token_id_type, + tokenIDs: %{ + type: "array", + array_type: @token_id_type + }, tokenSymbol: @token_symbol_type, tokenDecimal: @token_decimal_type, transactionIndex: @transaction_index_type, @@ -1400,12 +1420,12 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to descending order. Available values: asc, desc" }, %{ - key: "startblock", + key: "start_block", type: "integer", description: "A nonnegative integer that represents the starting block number." }, %{ - key: "endblock", + key: "end_block", type: "integer", description: "A nonnegative integer that represents the ending block number." }, @@ -1422,7 +1442,7 @@ defmodule BlockScoutWeb.Etherscan do "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." }, %{ - key: "filterby", + key: "filter_by", type: "string", description: """ A string representing the field to filter by. If none is given @@ -1431,12 +1451,12 @@ defmodule BlockScoutWeb.Etherscan do """ }, %{ - key: "starttimestamp", + key: "start_timestamp", type: "unix timestamp", description: "Represents the starting block timestamp." }, %{ - key: "endtimestamp", + key: "end_timestamp", type: "unix timestamp", description: "Represents the ending block timestamp." } @@ -1493,13 +1513,13 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to ascending order. Available values: asc, desc. WARNING: Only available if 'address' is provided." }, %{ - key: "startblock", + key: "start_block", type: "integer", description: "A nonnegative integer that represents the starting block number. WARNING: Only available if 'address' is provided." }, %{ - key: "endblock", + key: "end_block", type: "integer", description: "A nonnegative integer that represents the ending block number. WARNING: Only available if 'address' is provided." @@ -1568,12 +1588,12 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to ascending order. Available values: asc, desc" }, %{ - key: "startblock", + key: "start_block", type: "integer", description: "A nonnegative integer that represents the starting block number." }, %{ - key: "endblock", + key: "end_block", type: "integer", description: "A nonnegative integer that represents the ending block number." }, @@ -2310,6 +2330,18 @@ defmodule BlockScoutWeb.Etherscan do type: "string", description: "Ensures that none of the returned contracts were decompiled with the provided version. Ignored unless filtering for decompiled contracts." + }, + %{ + key: "verified_at_start_timestamp", + type: "unix timestamp", + description: + "Represents the starting timestamp when contracts verified. Taking into account only with `verified` filter." + }, + %{ + key: "verified_at_end_timestamp", + type: "unix timestamp", + description: + "Represents the ending timestamp when contracts verified. Taking into account only with `verified` filter." } ], responses: [ diff --git a/apps/block_scout_web/lib/block_scout_web/models/get_address_tags.ex b/apps/block_scout_web/lib/block_scout_web/models/get_address_tags.ex new file mode 100644 index 000000000000..8f0283cbda6b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/models/get_address_tags.ex @@ -0,0 +1,74 @@ +defmodule BlockScoutWeb.Models.GetAddressTags do + @moduledoc """ + Get various types of tags associated with the address + """ + + import Ecto.Query, only: [from: 2] + + alias Explorer.Account.{TagAddress, WatchlistAddress} + alias Explorer.Repo + alias Explorer.Tags.{AddressTag, AddressToTag} + + def get_address_tags(nil, nil), + do: %{common_tags: [], personal_tags: [], watchlist_names: []} + + def get_address_tags(address_hash, current_user) when not is_nil(address_hash) do + %{ + common_tags: get_tags_on_address(address_hash), + personal_tags: get_personal_tags(address_hash, current_user), + watchlist_names: get_watchlist_names_on_address(address_hash, current_user) + } + end + + def get_address_tags(_, _), do: %{common_tags: [], personal_tags: [], watchlist_names: []} + + def get_public_tags(address_hash) when not is_nil(address_hash) do + %{ + common_tags: get_tags_on_address(address_hash) + } + end + + def get_tags_on_address(address_hash) when not is_nil(address_hash) do + query = + from( + tt in AddressTag, + left_join: att in AddressToTag, + on: tt.id == att.tag_id, + where: att.address_hash == ^address_hash, + where: tt.label != ^"validator", + select: %{label: tt.label, display_name: tt.display_name, address_hash: att.address_hash} + ) + + Repo.all(query) + end + + def get_tags_on_address(_), do: [] + + def get_personal_tags(address_hash, %{id: id}) when not is_nil(address_hash) do + query = + from( + ta in TagAddress, + where: ta.address_hash_hash == ^address_hash, + where: ta.identity_id == ^id, + select: %{label: ta.name, display_name: ta.name, address_hash: ta.address_hash} + ) + + Repo.account_repo().all(query) + end + + def get_personal_tags(_, _), do: [] + + def get_watchlist_names_on_address(address_hash, %{watchlist_id: watchlist_id}) when not is_nil(address_hash) do + query = + from( + wa in WatchlistAddress, + where: wa.address_hash_hash == ^address_hash, + where: wa.watchlist_id == ^watchlist_id, + select: %{label: wa.name, display_name: wa.name} + ) + + Repo.account_repo().all(query) + end + + def get_watchlist_names_on_address(_, _), do: [] +end diff --git a/apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex b/apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex new file mode 100644 index 000000000000..2f3ef3f029bb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex @@ -0,0 +1,48 @@ +defmodule BlockScoutWeb.Models.GetTransactionTags do + @moduledoc """ + Get various types of tags associated with the transaction + """ + + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2, get_tags_on_address: 1] + + alias Explorer.Account.TagTransaction + alias Explorer.Chain.Transaction + alias Explorer.Repo + + def get_transaction_with_addresses_tags( + %Transaction{} = transaction, + %{id: identity_id, watchlist_id: watchlist_id} + ) do + tx_tag = get_transaction_tags(transaction.hash, %{id: identity_id}) + addresses_tags = get_addresses_tags_for_transaction(transaction, %{id: identity_id, watchlist_id: watchlist_id}) + Map.put(addresses_tags, :personal_tx_tag, tx_tag) + end + + def get_transaction_with_addresses_tags(%Transaction{} = transaction, _), + do: %{ + common_tags: get_tags_on_address(transaction.to_address_hash), + personal_tags: [], + watchlist_names: [], + personal_tx_tag: nil + } + + def get_transaction_tags(transaction_hash, %{id: identity_id}) do + Repo.account_repo().get_by(TagTransaction, tx_hash_hash: transaction_hash, identity_id: identity_id) + end + + def get_transaction_tags(_, _), do: nil + + def get_addresses_tags_for_transaction( + %Transaction{} = transaction, + %{id: identity_id, watchlist_id: watchlist_id} + ) do + from_tags = get_address_tags(transaction.from_address_hash, %{id: identity_id, watchlist_id: watchlist_id}) + to_tags = get_address_tags(transaction.to_address_hash, %{id: identity_id, watchlist_id: watchlist_id}) + + %{ + common_tags: get_tags_on_address(transaction.to_address_hash), + personal_tags: Enum.dedup(from_tags.personal_tags ++ to_tags.personal_tags), + watchlist_names: Enum.dedup(from_tags.watchlist_names ++ to_tags.watchlist_names) + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/models/user_from_auth.ex b/apps/block_scout_web/lib/block_scout_web/models/user_from_auth.ex new file mode 100644 index 000000000000..058b1605409b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/models/user_from_auth.ex @@ -0,0 +1,136 @@ +defmodule BlockScoutWeb.Models.UserFromAuth do + @moduledoc """ + Retrieve the user information from an auth request + """ + require Logger + require Poison + + alias Explorer.Account.Identity + alias Explorer.Repo + alias Ueberauth.Auth + + import Ecto.Query, only: [from: 2] + + def find_or_create(%Auth{} = auth, api_call? \\ false) do + case find_identity(auth) do + [] -> + case create_identity(auth) do + %Identity{} = identity -> + {:ok, return_value(identity, auth, api_call?)} + + {:error, changeset} -> + {:error, changeset} + end + + [%{} = identity | _] -> + update_identity(identity, update_identity_map(auth)) + {:ok, return_value(identity, auth, api_call?)} + end + end + + defp return_value(identity, _auth, true) do + identity + end + + defp return_value(identity, auth, false) do + basic_info(auth, identity) + end + + defp create_identity(auth) do + with {:ok, %Identity{} = identity} <- Repo.account_repo().insert(new_identity(auth)), + {:ok, _watchlist} <- add_watchlist(identity) do + identity + end + end + + defp update_identity(identity, attrs) do + identity + |> Identity.changeset(attrs) + |> Repo.account_repo().update() + end + + defp new_identity(auth) do + %Identity{ + uid: auth.uid, + uid_hash: auth.uid, + email: email_from_auth(auth), + name: name_from_auth(auth), + nickname: nickname_from_auth(auth), + avatar: avatar_from_auth(auth) + } + end + + defp add_watchlist(identity) do + watchlist = Ecto.build_assoc(identity, :watchlists, %{}) + + with {:ok, _} <- Repo.account_repo().insert(watchlist), + do: {:ok, identity} + end + + def find_identity(auth_or_uid) do + Repo.account_repo().all(query_identity(auth_or_uid)) + end + + def query_identity(%Auth{} = auth) do + from(i in Identity, where: i.uid_hash == ^auth.uid) + end + + def query_identity(id) do + from(i in Identity, where: i.id == ^id) + end + + defp basic_info(auth, identity) do + %{watchlists: [watchlist | _]} = Repo.account_repo().preload(identity, :watchlists) + + %{ + id: identity.id, + uid: auth.uid, + email: email_from_auth(auth), + name: name_from_auth(auth), + nickname: nickname_from_auth(auth), + avatar: avatar_from_auth(auth), + watchlist_id: watchlist.id + } + end + + defp update_identity_map(auth) do + %{ + email: email_from_auth(auth), + name: name_from_auth(auth), + nickname: nickname_from_auth(auth), + avatar: avatar_from_auth(auth) + } + end + + # github does it this way + defp avatar_from_auth(%{info: %{urls: %{avatar_url: image}}}), do: image + + # facebook does it this way + defp avatar_from_auth(%{info: %{image: image}}), do: image + + # default case if nothing matches + defp avatar_from_auth(auth) do + Logger.warn(auth.provider <> " needs to find an avatar URL!") + Logger.debug(Poison.encode!(auth)) + nil + end + + defp email_from_auth(%{info: %{email: email}}), do: email + + defp nickname_from_auth(%{info: %{nickname: nickname}}), do: nickname + + defp name_from_auth(%{info: %{name: name}}) + when name != "" and not is_nil(name), + do: name + + defp name_from_auth(%{info: info}) do + [info.first_name, info.last_name, info.nickname] + |> Enum.map(&(&1 |> to_string() |> String.trim())) + |> case do + ["", "", nick] -> nick + ["", lastname, _] -> lastname + [name, "", _] -> name + [name, lastname, _] -> name <> " " <> lastname + end + end +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 cf398ba3a050..9e0264e0e29a 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, + AddressContractVerificationViaMultiPartFilesView, AddressContractVerificationViaStandardJsonInputView, AddressContractVerificationVyperView, Endpoint @@ -183,7 +184,7 @@ defmodule BlockScoutWeb.Notifier do today = Date.utc_today() [{:history_size, history_size}] = - Application.get_env(:block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, 30) + Application.get_env(:block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, {:history_size, 30}) x_days_back = Date.add(today, -1 * history_size) @@ -206,14 +207,13 @@ defmodule BlockScoutWeb.Notifier do end def select_contract_type_and_form_view(params) do - verification_from_metadata_json? = - Map.has_key?(params, "verification_type") && Map.get(params, "verification_type") == "json:metadata" + verification_from_metadata_json? = check_verification_type(params, "json:metadata") - verification_from_standard_json_input? = - Map.has_key?(params, "verification_type") && Map.get(params, "verification_type") == "json:standard" + verification_from_standard_json_input? = check_verification_type(params, "json:standard") - verification_from_vyper? = - Map.has_key?(params, "verification_type") && Map.get(params, "verification_type") == "vyper" + verification_from_vyper? = check_verification_type(params, "vyper") + + verification_from_multi_part_files? = check_verification_type(params, "multi-part-files") compiler = if verification_from_vyper?, do: :vyper, else: :solc @@ -222,12 +222,16 @@ defmodule BlockScoutWeb.Notifier do verification_from_standard_json_input? -> AddressContractVerificationViaStandardJsonInputView verification_from_metadata_json? -> AddressContractVerificationViaJsonView verification_from_vyper? -> AddressContractVerificationVyperView + verification_from_multi_part_files? -> AddressContractVerificationViaMultiPartFilesView true -> AddressContractVerificationViaFlattenedCodeView end %{view: view, compiler: compiler} end + defp check_verification_type(params, type), + do: Map.has_key?(params, "verification_type") && Map.get(params, "verification_type") == type + @doc """ Broadcast the percentage of blocks indexed so far. """ @@ -238,6 +242,16 @@ defmodule BlockScoutWeb.Notifier do }) end + @doc """ + Broadcast the percentage of pending block operations indexed so far. + """ + def broadcast_internal_transactions_indexed_ratio(ratio, finished?) do + Endpoint.broadcast("blocks:indexing_internal_transactions", "index_status", %{ + ratio: Decimal.to_string(ratio), + finished: finished? + }) + end + defp broadcast_latest_block?(block, last_broadcasted_block_number) do cond do last_broadcasted_block_number == 0 || last_broadcasted_block_number == block.number - 1 || @@ -367,8 +381,6 @@ defmodule BlockScoutWeb.Notifier do end defp broadcast_token_transfer(token_transfer, event) do - Endpoint.broadcast("token_transfers:#{token_transfer.transaction_hash}", event, %{}) - Endpoint.broadcast("addresses:#{token_transfer.from_address_hash}", event, %{ address: token_transfer.from_address, token_transfer: token_transfer diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex new file mode 100644 index 000000000000..9b0b37224018 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -0,0 +1,139 @@ +defmodule BlockScoutWeb.PagingHelper do + @moduledoc """ + Helper for fetching filters and other url query paramters + """ + import Explorer.Chain, only: [string_to_transaction_hash: 1] + alias Explorer.PagingOptions + + @page_size 50 + @default_paging_options %PagingOptions{page_size: @page_size + 1} + @allowed_filter_labels ["validated", "pending"] + @allowed_type_labels ["coin_transfer", "contract_call", "contract_creation", "token_transfer", "token_creation"] + @allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155"] + + def paging_options(%{"block_number" => block_number_string, "index" => index_string}, [:validated | _]) do + with {block_number, ""} <- Integer.parse(block_number_string), + {index, ""} <- Integer.parse(index_string) do + [paging_options: %{@default_paging_options | key: {block_number, index}}] + else + _ -> + [paging_options: @default_paging_options] + end + end + + def paging_options(%{"inserted_at" => inserted_at_string, "hash" => hash_string}, [:pending | _]) do + with {:ok, inserted_at, _} <- DateTime.from_iso8601(inserted_at_string), + {:ok, hash} <- string_to_transaction_hash(hash_string) do + [paging_options: %{@default_paging_options | key: {inserted_at, hash}, is_pending_tx: true}] + else + _ -> + [paging_options: @default_paging_options] + end + end + + def paging_options(_params, _filter), do: [paging_options: @default_paging_options] + + def token_transfers_types_options(%{"type" => filters}) do + [ + token_type: filters |> String.upcase() |> parse_filter(@allowed_token_transfer_type_labels) + ] + end + + def token_transfers_types_options(_), do: [token_type: []] + + # sobelow_skip ["DOS.StringToAtom"] + def filter_options(%{"filter" => filter}, fallback) do + filter = filter |> parse_filter(@allowed_filter_labels) |> Enum.map(&String.to_atom/1) + if(filter == [], do: [fallback], else: filter) + end + + def filter_options(_params, fallback), do: [fallback] + + # sobelow_skip ["DOS.StringToAtom"] + def type_filter_options(%{"type" => type}) do + [type: type |> parse_filter(@allowed_type_labels) |> Enum.map(&String.to_atom/1)] + end + + def type_filter_options(_params), do: [type: []] + + def method_filter_options(%{"method" => method}) do + [method: parse_method_filter(method)] + end + + def method_filter_options(_params), do: [method: []] + + def parse_filter("[" <> filter, allowed_labels) do + filter + |> String.trim_trailing("]") + |> parse_filter(allowed_labels) + end + + def parse_filter(filter, allowed_labels) when is_binary(filter) do + filter + |> String.split(",") + |> Enum.filter(fn label -> Enum.member?(allowed_labels, label) end) + |> Enum.uniq() + end + + def parse_method_filter("[" <> filter) do + filter + |> String.trim_trailing("]") + |> parse_method_filter() + end + + def parse_method_filter(filter) do + filter + |> String.split(",") + |> Enum.uniq() + end + + def select_block_type(%{"type" => type}) do + case String.downcase(type) do + "uncle" -> + [ + necessity_by_association: %{ + :transactions => :optional, + [miner: :names] => :optional, + :nephews => :required, + :rewards => :optional + }, + block_type: "Uncle" + ] + + "reorg" -> + [ + necessity_by_association: %{ + :transactions => :optional, + [miner: :names] => :optional, + :rewards => :optional + }, + block_type: "Reorg" + ] + + _ -> + select_block_type(nil) + end + end + + def select_block_type(_), + do: [ + necessity_by_association: %{ + :transactions => :optional, + [miner: :names] => :optional, + :rewards => :optional + }, + block_type: "Block" + ] + + def delete_parameters_from_next_page_params(params) when is_map(params) do + params + |> Map.delete("block_hash_or_number") + |> Map.delete("transaction_hash") + |> Map.delete("address_hash") + |> Map.delete("type") + |> Map.delete("method") + |> Map.delete("filter") + end + + def delete_parameters_from_next_page_params(_), do: nil +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/check_account_api.ex b/apps/block_scout_web/lib/block_scout_web/plug/check_account_api.ex new file mode 100644 index 000000000000..80c5301bce15 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/check_account_api.ex @@ -0,0 +1,21 @@ +defmodule BlockScoutWeb.Plug.CheckAccountAPI do + @moduledoc """ + Checks if the Account functionality enabled for API level. + """ + import Plug.Conn + + alias Explorer.Account + + def init(opts), do: opts + + def call(conn, _opts) do + if Account.enabled?() do + conn + else + conn + |> put_resp_content_type("application/json") + |> send_resp(404, Jason.encode!(%{message: "Account functionality is disabled"})) + |> halt() + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/check_account_web.ex b/apps/block_scout_web/lib/block_scout_web/plug/check_account_web.ex new file mode 100644 index 000000000000..00a3af4e002e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/check_account_web.ex @@ -0,0 +1,31 @@ +defmodule BlockScoutWeb.Plug.CheckAccountWeb do + @moduledoc """ + Checks if the Account functionality enabled for web interface. + """ + import Phoenix.Controller + alias Phoenix.View + import Plug.Conn + + alias Explorer.Account + + def init(opts), do: opts + + def call(conn, _opts) do + if Account.enabled?() do + conn + else + inner_view = + View.render( + BlockScoutWeb.PageNotFoundView, + "index.html", + token: nil + ) + + conn + |> put_status(404) + |> put_view(BlockScoutWeb.LayoutView) + |> render(:app, inner_content: inner_view) + |> halt() + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/check_api_v2.ex b/apps/block_scout_web/lib/block_scout_web/plug/check_api_v2.ex new file mode 100644 index 000000000000..95269a203932 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/check_api_v2.ex @@ -0,0 +1,21 @@ +defmodule BlockScoutWeb.Plug.CheckApiV2 do + @moduledoc """ + Checks if the API V2 enabled. + """ + import Plug.Conn + + alias BlockScoutWeb.API.V2, as: API_V2 + + def init(opts), do: opts + + def call(conn, _opts) do + if API_V2.enabled?() do + conn + else + conn + |> put_resp_content_type("application/json") + |> send_resp(404, Jason.encode!(%{message: "API V2 is disabled"})) + |> halt() + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex b/apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex new file mode 100644 index 000000000000..ff8d457f090b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex @@ -0,0 +1,223 @@ +defmodule BlockScoutWeb.Plug.RedisCookie do + @moduledoc """ + Extended version of Plug.Session.COOKIE from https://github.com/elixir-plug/plug/blob/main/lib/plug/session/cookie.ex + Added Redis to have a possibility to invalidate session + """ + + require Logger + @behaviour Plug.Session.Store + + alias Plug.Crypto + alias Plug.Crypto.{KeyGenerator, MessageEncryptor, MessageVerifier} + + @impl true + def init(opts) do + opts + |> build_opts() + |> build_rotating_opts(opts[:rotating_options]) + |> Map.delete(:secret_key_base) + end + + @impl true + def get(conn, raw_cookie, opts) do + opts = Map.put(opts, :secret_key_base, conn.secret_key_base) + + [opts | opts.rotating_options] + |> Enum.find_value(:error, &read_raw_cookie(raw_cookie, &1)) + |> decode(opts.serializer, opts.log) + |> check_in_redis(raw_cookie) + end + + @impl true + def put(conn, _sid, term, opts) do + %{serializer: serializer, key_opts: key_opts, signing_salt: signing_salt} = opts + binary = encode(term, serializer) + + opts + |> case do + %{encryption_salt: nil} -> + MessageVerifier.sign(binary, derive(conn.secret_key_base, signing_salt, key_opts)) + + %{encryption_salt: encryption_salt} -> + MessageEncryptor.encrypt( + binary, + derive(conn.secret_key_base, encryption_salt, key_opts), + derive(conn.secret_key_base, signing_salt, key_opts) + ) + end + |> store_to_redis() + end + + @impl true + def delete(_conn, sid, _opts) do + remove_from_redis(sid) + :ok + end + + defp encode(term, :external_term_format) do + :erlang.term_to_binary(term) + end + + defp encode(term, serializer) do + {:ok, binary} = serializer.encode(term) + binary + end + + defp decode({:ok, binary}, :external_term_format, log) do + {:term, + try do + Crypto.non_executable_binary_to_term(binary) + rescue + e -> + Logger.log( + log, + "Plug.Session could not decode incoming session cookie. Reason: " <> + Exception.message(e) + ) + + %{} + end} + end + + defp decode({:ok, binary}, serializer, _log) do + case serializer.decode(binary) do + {:ok, term} -> {:custom, term} + _ -> {:custom, %{}} + end + end + + defp decode(:error, _serializer, false) do + {nil, %{}} + end + + defp decode(:error, _serializer, log) do + Logger.log( + log, + "Plug.Session could not verify incoming session cookie. " <> + "This may happen when the session settings change or a stale cookie is sent." + ) + + {nil, %{}} + end + + defp prederive(secret_key_base, value, key_opts) + when is_binary(secret_key_base) and is_binary(value) do + {:prederived, derive(secret_key_base, value, Keyword.delete(key_opts, :cache))} + end + + defp prederive(_secret_key_base, value, _key_opts) do + value + end + + defp derive(_secret_key_base, {:prederived, value}, _key_opts) do + value + end + + defp derive(secret_key_base, {module, function, args}, key_opts) do + derive(secret_key_base, apply(module, function, args), key_opts) + end + + defp derive(secret_key_base, key, key_opts) do + secret_key_base + |> validate_secret_key_base() + |> KeyGenerator.generate(key, key_opts) + end + + defp validate_secret_key_base(nil), + do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be set") + + defp validate_secret_key_base(secret_key_base) when byte_size(secret_key_base) < 64, + do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be at least 64 bytes") + + defp validate_secret_key_base(secret_key_base), do: secret_key_base + + defp check_signing_salt(opts) do + case opts[:signing_salt] do + nil -> raise ArgumentError, "cookie store expects :signing_salt as option" + salt -> salt + end + end + + defp check_serializer(serializer) when is_atom(serializer), do: serializer + + defp check_serializer(_), + do: raise(ArgumentError, "cookie store expects :serializer option to be a module") + + defp read_raw_cookie(raw_cookie, opts) do + signing_salt = derive(opts.secret_key_base, opts.signing_salt, opts.key_opts) + + opts + |> case do + %{encryption_salt: nil} -> + MessageVerifier.verify(raw_cookie, signing_salt) + + %{encryption_salt: _} -> + encryption_salt = derive(opts.secret_key_base, opts.encryption_salt, opts.key_opts) + + MessageEncryptor.decrypt(raw_cookie, encryption_salt, signing_salt) + end + |> case do + :error -> nil + result -> result + end + end + + defp build_opts(opts) do + encryption_salt = opts[:encryption_salt] + signing_salt = check_signing_salt(opts) + + iterations = Keyword.get(opts, :key_iterations, 1000) + length = Keyword.get(opts, :key_length, 32) + digest = Keyword.get(opts, :key_digest, :sha256) + log = Keyword.get(opts, :log, :debug) + secret_key_base = Keyword.get(opts, :secret_key_base) + key_opts = [iterations: iterations, length: length, digest: digest, cache: Plug.Keys] + + serializer = check_serializer(opts[:serializer] || :external_term_format) + + %{ + secret_key_base: secret_key_base, + encryption_salt: prederive(secret_key_base, encryption_salt, key_opts), + signing_salt: prederive(secret_key_base, signing_salt, key_opts), + key_opts: key_opts, + serializer: serializer, + log: log + } + end + + defp build_rotating_opts(opts, rotating_opts) when is_list(rotating_opts) do + Map.put(opts, :rotating_options, Enum.map(rotating_opts, &build_opts/1)) + end + + defp build_rotating_opts(opts, _), do: Map.put(opts, :rotating_options, []) + + defp store_to_redis(cookie) do + Redix.command(:redix, ["SET", hash(cookie), 1]) + + cookie + end + + defp remove_from_redis(sid) do + Redix.command(:redix, ["DEL", sid]) + end + + defp check_in_redis({sid, map}, _cookie) when is_nil(sid) or map == %{}, do: {nil, %{}} + + defp check_in_redis({_sid, session}, cookie) do + hash = hash(cookie) + + case Redix.command(:redix, ["GET", hash]) do + {:ok, one} when one in [1, "1"] -> + {hash, session} + + _ -> + {nil, %{}} + end + end + + defp hash(cookie) do + :sha256 + |> :crypto.hash(cookie) + |> Base.encode16() + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex b/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex index fe9646379488..adfe1b793d68 100644 --- a/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex +++ b/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex @@ -13,9 +13,11 @@ defmodule BlockScoutWeb.Resolvers.Transaction do end def get_by(%Address{hash: address_hash}, args, _) do + connection_args = Map.take(args, [:after, :before, :first, :last]) + address_hash - |> GraphQL.address_to_transactions_query() - |> Connection.from_query(&Repo.all/1, args, options(args)) + |> GraphQL.address_to_transactions_query(args.order) + |> Connection.from_query(&Repo.all/1, connection_args, options(args)) end defp options(%{before: _}), do: [] diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index 78a3bb2cd826..0fa78f97e732 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -4,11 +4,11 @@ defmodule BlockScoutWeb.Router do alias BlockScoutWeb.Plug.GraphQL alias BlockScoutWeb.{ApiRouter, WebRouter} - if Application.get_env(:block_scout_web, ApiRouter)[:wobserver_enabled] do + if Application.compile_env(:block_scout_web, ApiRouter)[:wobserver_enabled] do forward("/wobserver", Wobserver.Web.Router) end - if Application.get_env(:block_scout_web, :admin_panel_enabled) do + if Application.compile_env(:block_scout_web, :admin_panel_enabled) do forward("/admin", BlockScoutWeb.AdminRouter) end @@ -26,7 +26,7 @@ defmodule BlockScoutWeb.Router do forward("/api", ApiRouter) - if Application.get_env(:block_scout_web, ApiRouter)[:reading_enabled] do + if Application.compile_env(:block_scout_web, ApiRouter)[:reading_enabled] do # Needs to be 200 to support the schema introspection for graphiql @max_complexity 200 @@ -59,7 +59,7 @@ defmodule BlockScoutWeb.Router do get("/eth-rpc-api-docs", APIDocsController, :eth_rpc) end - url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url] + url_params = Application.compile_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url] api_path = url_params[:api_path] path = url_params[:path] @@ -77,7 +77,7 @@ defmodule BlockScoutWeb.Router do end end - if Application.get_env(:block_scout_web, WebRouter)[:enabled] do + if Application.compile_env(:block_scout_web, WebRouter)[:enabled] do forward("/", BlockScoutWeb.WebRouter) else scope "/", BlockScoutWeb do diff --git a/apps/block_scout_web/lib/block_scout_web/schema/scalars.ex b/apps/block_scout_web/lib/block_scout_web/schema/scalars.ex index b7a8939a3986..51ebae097d4d 100644 --- a/apps/block_scout_web/lib/block_scout_web/schema/scalars.ex +++ b/apps/block_scout_web/lib/block_scout_web/schema/scalars.ex @@ -113,4 +113,9 @@ defmodule BlockScoutWeb.Schema.Scalars do value(:reward) value(:selfdestruct) end + + enum :sort_order do + value(:asc) + value(:desc) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/schema/types.ex b/apps/block_scout_web/lib/block_scout_web/schema/types.ex index f1ecbbbe20e1..426d9909f59b 100644 --- a/apps/block_scout_web/lib/block_scout_web/schema/types.ex +++ b/apps/block_scout_web/lib/block_scout_web/schema/types.ex @@ -33,6 +33,7 @@ defmodule BlockScoutWeb.Schema.Types do connection field(:transactions, node_type: :transaction) do arg(:count, :integer) + arg(:order, type: :sort_order, default_value: :desc) resolve(&Transaction.get_by/3) complexity(fn @@ -114,9 +115,11 @@ defmodule BlockScoutWeb.Schema.Types do """ node object(:token_transfer, id_fetcher: &token_transfer_id_fetcher/2) do field(:amount, :decimal) + field(:amounts, list_of(:decimal)) field(:block_number, :integer) field(:log_index, :integer) field(:token_id, :decimal) + field(:token_ids, list_of(:decimal)) field(:from_address_hash, :address_hash) field(:to_address_hash, :address_hash) field(:token_contract_address_hash, :address_hash) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/form.html.eex new file mode 100644 index 000000000000..a425a078b5d8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/form.html.eex @@ -0,0 +1,34 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :api_keys %> +
+
+
+

<%=if @method == :update, do: gettext("Update"), else: gettext("Add") %> <%= gettext "API key"%>

+ +
+
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/index.html.eex new file mode 100644 index 000000000000..72ebbe8782a0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/index.html.eex @@ -0,0 +1,51 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :api_keys %> +
+
+
+

<%= gettext "API keys" %>

+
+ <%= if Enum.count(@api_keys) < Key.get_max_api_keys_count() do %> +
+
+ <%= gettext "Create an API key to use with your RPC и EthRPC API requests." %> <%= gettext "Learn more" %> +
+
+ <% else %> +
+
+ <%= gettext "You can create 3 API keys per account." %> <%= gettext "Learn more" %> +
+
+ <% end %> +
+
+ <%= if @api_keys != [] do %> + + + + + + + + + + + <%= Enum.map(@api_keys, fn key -> + render("row.html", api_key: key, conn: @conn) + end) %> + +
<%= gettext "Name" %><%= gettext "API key" %>
+ <% end %> +
+
+ <%= if Enum.count(@api_keys) < Key.get_max_api_keys_count() do %> + <%= gettext "Add API key" %> + <% end %> +
+
+
+
+ +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/row.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/row.html.eex new file mode 100644 index 000000000000..1d834336fb72 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/api_key/row.html.eex @@ -0,0 +1,18 @@ + + <%= @api_key.name %> + + <%= @api_key.value %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @api_key.value, aria_label: gettext("Copy API key"), title: gettext("Copy API key"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> + + +
+ + +
+ <%= gettext("Remove") %> + + + <%= link gettext("Edit"), to: api_key_path(@conn, :edit, @api_key.value) %> + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/auth/profile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/auth/profile.html.eex new file mode 100644 index 000000000000..618c877f1691 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/auth/profile.html.eex @@ -0,0 +1,36 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :profile %> +
+
+
+

Profile

+
+
+ <%= @user.nickname %> +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/common/_nav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/common/_nav.html.eex new file mode 100644 index 000000000000..cf28ad5d855c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/common/_nav.html.eex @@ -0,0 +1,25 @@ + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/form.html.eex new file mode 100644 index 000000000000..c54477c82466 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/form.html.eex @@ -0,0 +1,39 @@ +<% abi = format_abi(@custom_abi) %> +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :custom_abis %> +
+
+
+

<%=if @method == :update, do: gettext("Update"), else: gettext("Add") %> <%= gettext "Custom ABI"%>

+ +
+
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/index.html.eex new file mode 100644 index 000000000000..409a4ffaa027 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/index.html.eex @@ -0,0 +1,51 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :custom_abis %> +
+
+
+

<%= gettext "Custom ABI" %>

+
+ <%= if Enum.count(@custom_abis) < CustomABI.get_max_custom_abis_count() do %> +
+
+ <%= gettext "Create a Custom ABI to interact with contracts." %> +
+
+ <% else %> +
+
+ <%= gettext "You can create up to 15 Custom ABIs per account." %> +
+
+ <% end %> +
+
+ <%= if @custom_abis != [] do %> + + + + + + + + + + + <%= Enum.map(@custom_abis, fn key -> + render("row.html", custom_abi: key, conn: @conn) + end) %> + +
<%= gettext "Name" %><%= gettext "Contract Address" %>
+ <% end %> +
+
+ <%= if Enum.count(@custom_abis) < CustomABI.get_max_custom_abis_count() do %> + <%= gettext "Add Custom ABI" %> + <% end %> +
+
+
+
+ +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/row.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/row.html.eex new file mode 100644 index 000000000000..36c2740aa809 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/row.html.eex @@ -0,0 +1,18 @@ + + <%= @custom_abi.name %> + + <%= link(@custom_abi.address_hash, to: address_contract_path(@conn, :index, @custom_abi.address_hash)) %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @custom_abi.address_hash, aria_label: gettext("Copy Contract Address"), title: gettext("Copy Contract Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> + + +
+ + +
+ <%= gettext("Remove") %> + + + <%= link gettext("Edit"), to: custom_abi_path(@conn, :edit, @custom_abi.id) %> + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex new file mode 100644 index 000000000000..a0b78f313da7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex @@ -0,0 +1,8 @@ +
+ <%= label @f, :addresses, gettext("Address*"), class: "control-label", style: "font-size: 14px" %> +
+ <%= array_input @f, :addresses, maxlength: 42, size: 70, placeholder: gettext "Smart contract / Address (0x...)" %> + <%= array_add_button @f, :addresses, maxlength: 42, size: 70, placeholder: gettext "Smart contract / Address (0x...)" %> +
+ <%= error_tag @f, :addresses, class: "text-danger form-error pt-0" %> +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/form.html.eex new file mode 100644 index 000000000000..86cfd1df22b7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/form.html.eex @@ -0,0 +1,72 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :public_tags %> +
+
+
+

<%=if @method == :update, do: gettext("Request to edit a public tag/label"), else: gettext("Request a public tag/label") %>

+ +
+
+
+
+ +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/index.html.eex new file mode 100644 index 000000000000..bd9451947338 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/index.html.eex @@ -0,0 +1,44 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :public_tags %> +
+
+
+

<%= gettext "Public tags" %>

+
+
+
+ <%= gettext "You can request a public category tag which is displayed to all Blockscout users. Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag. Clicking a tag opens a page with related information and helps provide context and data organization. Requests are sent to a moderator for review and approval. This process can take several days." %> +
+
+
+
+ <%= if @public_tags_requests != [] do %> + + + + + + + + + + + + <%= Enum.map(@public_tags_requests, fn x -> + render("row.html", public_tags_request: x, conn: @conn) + end) %> + +
<%= gettext "Public tag" %><%= gettext "Smart contract / Address" %><%= gettext "Submission date" %>
+ <% end %> +
+
+ <%= if Enum.count(@public_tags_requests) < PublicTagsRequest.get_max_public_tags_request_count() do %> + <%= gettext "Request to add public tag" %> + <% end %> +
+
+
+
+ +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/row.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/row.html.eex new file mode 100644 index 000000000000..f7d2772e479c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/row.html.eex @@ -0,0 +1,20 @@ + + + <%= for tag <- String.split(@public_tags_request.tags, ";") do %> +
<%= tag %>
+ <% end %> + + <%= Enum.join(@public_tags_request.addresses, "\n") %> + <%= Calendar.strftime(@public_tags_request.inserted_at, "%b %d, %Y") %> + + <%= link (render BlockScoutWeb.CommonComponentsView, "_svg_pen.html"), to: public_tags_request_path(@conn, :edit, @public_tags_request.id) %> + + +
+ + + +
+ <%= (render BlockScoutWeb.CommonComponentsView, "_svg_trash.html") %> + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/form.html.eex new file mode 100644 index 000000000000..ec4d930cf1f3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/form.html.eex @@ -0,0 +1,33 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :address_tags %> +
+
+
+

<%= gettext "Add address tag"%>

+ +
+
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/index.html.eex new file mode 100644 index 000000000000..511405cdda51 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/index.html.eex @@ -0,0 +1,41 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :address_tags %> +
+
+
+

<%= gettext "Address Tags" %>

+
+
+
+ <%= if @address_tags == [] do %> +
+
+ <%= gettext "You don't have address tags yet" %> +
+
+

+ <% else %> + + + + + + + + + + <%= Enum.map(@address_tags, fn at -> + render("row.html", address_tag: at, conn: @conn) + end) %> + +
<%= gettext "Name" %><%= gettext "Address" %><%= gettext "Action" %>
+ <% end %> +
+
+ <%= if Enum.count(@address_tags) < TagAddress.get_max_tags_count() do %> + <%= gettext "Add address tag" %> + <% end %> +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/row.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/row.html.eex new file mode 100644 index 000000000000..5fd4445741e7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/row.html.eex @@ -0,0 +1,15 @@ +<%= if @address_tag.address_hash do %> + + <%= @address_tag.name %> + +
+ <%= link(trimmed_hash(@address_tag.address_hash), to: address_path(@conn, :show, @address_tag.address_hash)) %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @address_tag.address_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> + + + <%= link "Remove Tag", to: tag_address_path(@conn, :delete, @address_tag.id), method: :delete %> +
+ + +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex new file mode 100644 index 000000000000..40d9c03f83be --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex @@ -0,0 +1,33 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :transaction_tags %> +
+
+
+

<%= gettext "Add transaction tag"%>

+ +
+
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex new file mode 100644 index 000000000000..c1cca8adec1c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex @@ -0,0 +1,41 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :transaction_tags %> +
+
+
+

<%= gettext "Transaction Tags" %>

+
+
+
+ <%= if @tx_tags == [] do %> +
+
+ <%= gettext "You don't have transaction tags yet" %> +
+
+

+ <% else %> + + + + + + + + + + <%= Enum.map(@tx_tags, fn at -> + render("row.html", tx_tag: at, conn: @conn) + end) %> + +
<%= gettext "Name" %><%= gettext "Transaction" %><%= gettext "Action" %>
+ <% end %> +
+
+ <%= if Enum.count(@tx_tags) < TagTransaction.get_max_tags_count() do %> + <%= gettext "Add transaction tag" %> + <% end %> +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex new file mode 100644 index 000000000000..55151ae0ac34 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex @@ -0,0 +1,18 @@ +<%= if @tx_tag.tx_hash do %> + + <%= @tx_tag.name %> + +
+ <%= link(@tx_tag.tx_hash, + to: transaction_path(BlockScoutWeb.Endpoint, :show, @tx_tag.tx_hash), + "data-test": "transaction_hash_link", + class: "text-truncate") %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @tx_tag.tx_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> +
+ + + <%= link "Remove Tag", to: tag_transaction_path(@conn, :delete, @tx_tag.id), method: :delete %> + + +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist/show.html.eex new file mode 100644 index 000000000000..592532f625e8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist/show.html.eex @@ -0,0 +1,42 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :watchlist %> +
+
+
+

<%= gettext "Watch list" %>

+
+
+
+ <%= if @watchlist.watchlist_addresses == [] do %> +
+
+ <%= gettext "You don't have addresses on you watchlist yet" %> +
+
+

+ <% else %> + + + + + + + + + + + <%= Enum.map(@watchlist.watchlist_addresses, fn wa -> + render(WatchlistAddressView, "row.html", watchlist_address: wa, exchange_rate: exchange_rate(), conn: @conn) + end) %> + +
<%= gettext "Name" %><%= gettext "Address" %><%= gettext "Balance" %><%= gettext "Actions" %>
+ <% end %> +
+
+ <%= if Enum.count(@watchlist.watchlist_addresses) < WatchlistAddress.get_max_watchlist_addresses_count() do %> + <%= gettext "Add address" %> + <% end %> +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/form.html.eex new file mode 100644 index 000000000000..c2a6dce56989 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/form.html.eex @@ -0,0 +1,91 @@ +
+
+ <%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :watchlist %> +
+
+
+

<%=if @method == :update, do: gettext("Edit Watch list address"), else: gettext "Add address to the Watch list" %>

+ +
+
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/row.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/row.html.eex new file mode 100644 index 000000000000..95c92d9b8f76 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/row.html.eex @@ -0,0 +1,29 @@ + + <%= @watchlist_address.name %> + +
+ <%= link(trimmed_hash(@watchlist_address.address_hash), to: address_path(@conn, :show, @watchlist_address.address_hash)) %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @watchlist_address.address_hash, aria_label: gettext("Copy From Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> +
+ + + <%= balance_ether(@watchlist_address.fetched_coin_balance) %> +
+ + + ( + ) + + + + <%= link(gettext("Edit"), to: watchlist_address_path(@conn, :edit, @watchlist_address.id)) %> + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_current_coin_balance.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_current_coin_balance.html.eex index 76ba32bf9f72..405f7e6899a2 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_current_coin_balance.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_current_coin_balance.html.eex @@ -8,7 +8,7 @@ data-placement="top" data-toggle="tooltip" data-html="true" - title='<%= "@ " <> usd_value <> "/" <> gettext("Ether") %>' + title='<%= "@ " <> usd_value <> "/" <> Explorer.coin_name() %>' > ) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_labels.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_labels.html.eex new file mode 100644 index 000000000000..dbfb5bac756e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_labels.html.eex @@ -0,0 +1,19 @@ +<%= for common_tag <- @tags.common_tags do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: common_tag.display_name, additional_classes: [tag_name_to_label(common_tag.label), "ml-1"] %> +<% end %> +<%= for personal_tag <- @tags.personal_tags do %> + <%= if personal_tag.address_hash do %> + <%= if personal_tag.label =~ "dark forest" do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tag.display_name, additional_classes: ["df", "ml-1"] %> + <% else %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tag.display_name, additional_classes: [tag_name_to_label(personal_tag.label), "ml-1"] %> + <% end %> + <% end %> +<% end %> +<%= for watchlist_name <- @tags.watchlist_names do %> + <%= if watchlist_name.label =~ "dark forest" do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: watchlist_name.display_name, additional_classes: ["df", "ml-1"] %> + <% else %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: watchlist_name.display_name, additional_classes: [tag_name_to_label(watchlist_name.label), "ml-1"] %> + <% 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 06b0954cb05e..c3dc9af56103 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 @@ -12,7 +12,7 @@ <%= 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) %>) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex index 0145c72a6448..ce533de13a2c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex @@ -76,7 +76,7 @@ <% end %> <% end %> - <%= if smart_contract_with_read_only_functions?(@address) do %> + <%= if smart_contract_with_read_only_functions?(@address) || has_address_custom_abi_with_read_functions?(@conn, @address.hash) do %> <%= link( gettext("Read Contract"), to: AccessHelpers.get_path(@conn, :address_read_contract_path, :index, @address.hash), @@ -90,14 +90,14 @@ class: "card-tab #{tab_status("read-proxy", @conn.request_path)}") %> <% end %> - <%= if smart_contract_with_write_functions?(@address) do %> + <%= if smart_contract_with_write_functions?(@address) || has_address_custom_abi_with_write_functions?(@conn, @address.hash) do %> <%= link( gettext("Write Contract"), to: AccessHelpers.get_path(@conn, :address_write_contract_path, :index, @address.hash), class: "card-tab #{tab_status("write-contract", @conn.request_path)}") %> <% end %> - <%= if smart_contract_with_write_functions?(@address) && @is_proxy do %> + <%= if smart_contract_with_write_functions?(@address) && @is_proxy do %> <%= link( gettext("Write Proxy"), to: AccessHelpers.get_path(@conn, :address_write_proxy_path, :index, @address.hash), 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 a1cfb6fa762a..57dba32da773 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 "Ether" %> <%= gettext "Addresses" %>

+

<%= Explorer.coin_name() %> <%= 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 %> 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 3a666bca45db..9a8abc9dd7b2 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 @@ -28,6 +28,7 @@ <% end %>

<%= address_title(@address) %> <%= gettext "Details" %>
+ <%= render BlockScoutWeb.AddressView, "_labels.html", address_hash: @address.hash, tags: @tags %> <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", @@ -37,25 +38,6 @@ aria_label: gettext("Copy Address"), title: gettext("Copy Address") %> <%= render BlockScoutWeb.CommonComponentsView, "_btn_qr_code.html" %> - <%= if validator_metadata = primary_validator_metadata(@address) do %> - - - - - - - - <% end %>

<%= @address %>

@@ -134,14 +116,16 @@ "data-test": "transaction_hash_link" ) %> <% else %> - + <% end %> <% end %> <%= if @is_proxy do %> - <% {implementation_address, name} = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || "0x0000000000000000000000000000000000000000" %> + <% {implementation_address_, name} = SmartContract.get_implementation_address_hash(@address.smart_contract) %> + <% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %>
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", @@ -162,7 +146,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Address balance in xDAI (doesn't include ERC20, ERC721, ERC1155 tokens).") %> + text: gettext("Address balance in") <> " " <> Explorer.coin_name() <> " " <> gettext("doesn't include ERC20, ERC721, ERC1155 tokens).") %> <%= gettext("Balance") %>
@@ -176,7 +160,7 @@ data-placement="top" data-toggle="tooltip" data-html="true" - title='<%= "@ " <> usd_value <> "/" <> gettext("Ether") %>' + title='<%= "@ " <> usd_value <> "/" <> Explorer.coin_name() %>' > ) @@ -304,9 +288,4 @@ <%= render BlockScoutWeb.CommonComponentsView, "_modal_qr_code.html", qr_code: qr_code(@address), title: @address %> - -<%= if validator_metadata do %> - <%= render BlockScoutWeb.AddressView, "_validator_metadata_modal.html", address_name: address_name, validator_metadata: validator_metadata %> -<% end %> - <%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex index fb0d17ce4d8f..9292206e045b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex @@ -3,7 +3,7 @@ <% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
@@ -21,7 +21,7 @@ <%= gettext("There was a problem loading the chart.") %> <%= 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 %> 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 7f2b8dbbe31f..63b350a958f8 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 @@ -5,10 +5,11 @@ <% additional_sources_from_twin = Chain.get_address_verified_twin_contract(@address.hash).additional_sources %> <% fully_verified = Chain.smart_contract_fully_verified?(@address.hash)%> <% additional_sources = if smart_contract_verified, do: @address.smart_contract_additional_sources, else: additional_sources_from_twin %> +<% visualize_sol2uml_enabled = Explorer.Visualize.Sol2uml.enabled?() %>
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> @@ -23,7 +24,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_info.html" %> <%= gettext("Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB") %> <%= link( - metadata_for_verification.address_hash, + metadata_for_verification.address_hash, to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)) %>.
<%= gettext("All metadata displayed below is from that contract. In order to verify current contract, click") %> <%= gettext("Verify & Publish") %> <%= gettext("button") %>
<%= link(gettext("Verify & Publish"), to: path, class: "button button-primary button-sm float-right ml-3", "data-test": "verify_and_publish") %> @@ -98,9 +99,22 @@

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

- +
+ <%= if visualize_sol2uml_enabled && !target_contract.is_vyper_contract && !is_nil(target_contract.abi) do %> + + +
+ + Sol2uml +
new
+
+
+
+ <% end %> + +
><%= target_contract.contract_source_code %>
         
@@ -117,18 +131,35 @@
<% end)%> -
-
-

<%= gettext "Contract ABI" %>

- -
-
-
<%= format_smart_contract_abi(target_contract.abi) %>
-            
-
-
+ <%= if !is_nil(target_contract.compiler_settings) do %> +
+
+

<%= gettext "Compiler Settings" %>

+ +
+
+
<%= format_smart_contract_abi(target_contract.compiler_settings) %>
+              
+
+
+ <% end %> + + <%= if !is_nil(target_contract.abi) do %> +
+
+

<%= gettext "Contract ABI" %>

+ +
+
+
<%= format_smart_contract_abi(target_contract.abi) %>
+              
+
+
+ <% end %> <% end %>
@@ -155,27 +186,21 @@ - <%= if match?({:selfdestructed, _}, contract_creation_code) do %> -
- <%= gettext("Verify & Publish") %> -
- <% else %> - <%= if !fully_verified do %> - <% path = address_verify_contract_path(@conn, :new, @address.hash) %> - <%= link( - gettext("Verify & Publish"), - to: path, - class: "button button-primary button-sm float-right ml-3", - "data-test": "verify_and_publish" - ) %> - <% end %> + <%= if !fully_verified do %> + <% path = address_verify_contract_path(@conn, :new, @address.hash) %> + <%= link( + gettext("Verify & Publish"), + to: path, + class: "button button-primary button-sm float-right ml-3", + "data-test": "verify_and_publish" + ) %> <% end %>
<%= creation_code(@address) %>
- <% end %> + <% end %> <%= if fully_verified do %>

<%= gettext "Deployed ByteCode" %>

@@ -192,20 +217,14 @@ - <%= if match?({:selfdestructed, _}, contract_creation_code) and !creation_code(@address) do %> -
- <%= gettext("Verify & Publish") %> -
- <% else %> - <%= if !fully_verified and !creation_code(@address) do %> - <% path = address_verify_contract_path(@conn, :new, @address.hash) %> - <%= link( - gettext("Verify & Publish"), - to: path, - class: "button button-primary button-sm float-right ml-3", - "data-test": "verify_and_publish" - ) %> - <% end %> + <%= if !fully_verified and !creation_code(@address) do %> + <% path = address_verify_contract_path(@conn, :new, @address.hash) %> + <%= link( + gettext("Verify & Publish"), + to: path, + class: "button button-primary button-sm float-right ml-3", + "data-test": "verify_and_publish" + ) %> <% end %>
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 24158e10ea7e..0a9f89f2ca55 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 @@ -33,6 +33,13 @@ <%= label :verify_via, :false, gettext("Via Sourcify: Sources and metadata JSON file"), class: "radio-text" %>
<% end %> + <%= if RustVerifierInterface.enabled?() do %> +
+ <%= radio_button f, :verify_via, false, class: "form-check-input verify-via-multi-part-files" %> +
+ <%= label :verify_via, :false, gettext("Via multi-part files"), class: "radio-text" %> +
+ <% end %>
<%= radio_button f, :verify_via, false, class: "form-check-input verify-vyper-contract" %>
@@ -95,7 +102,15 @@ <%= link( gettext("Next"), to: address_verify_contract_via_standard_json_input_path(@conn, :new, @address_hash), - id: "verify_via_standard_json_input", + id: "verify_via_standard_json_input_button", + class: "btn-full-primary mr-2", + style: "display: none;", + "data-button-loading": "animation" + ) %> + <%= link( + gettext("Next"), + to: address_verify_contract_via_multi_part_files_path(@conn, :new, @address_hash), + id: "verify_via_multi_part_files_button", class: "btn-full-primary mr-2", style: "display: none;", "data-button-loading": "animation" diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex index fd98aaf149e9..bd2a179d80ee 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex @@ -1,6 +1,6 @@
- <%= label @f, :compiler_version, gettext("Compiler") %> + <%= label :smart_contract, :compiler_version, gettext("Compiler") %>
<%= select @f, :compiler_version, @compiler_versions, class: "form-control border-rounded", "aria-describedby": "compiler-help-block", id: "smart_contract_compiler_version" %> <%= error_tag @f, :compiler_version, id: "compiler-help-block", class: "text-danger form-error" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex index fcc2109fc373..c269a84e79a1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex @@ -1,17 +1,17 @@
- <%= label @f, "Try to fetch constructor arguments automatically" %> + <%= label @f, :autodetect_constructor_args, gettext("Try to fetch constructor arguments automatically") %>
<%= radio_button @f, :autodetect_constructor_args, false, class: "form-check-input autodetectfalse" %>
- <%= label :autodetect_constructor_args, :false, gettext("No"), class: "radio-text" %> + <%= label @f, :autodetect_constructor_args_false, gettext("No"), class: "radio-text" %>
<%= radio_button @f, :autodetect_constructor_args, true, class: "form-check-input autodetecttrue", "aria-describedby": "autodetect_constructor_args-help-block" %>
- <%= label :autodetect_constructor_args, :true, gettext("Yes"), class: "radio-text" %> + <%= label @f, :autodetect_constructor_args_true, gettext("Yes"), class: "radio-text" %>
<%= error_tag @f, :autodetect_constructor_args, id: "autodetect_constructor_args-help-block", class: "text-danger form-error" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex index 275de35cbec9..c452549a7069 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex @@ -1,21 +1,21 @@
-
- <%= label @f, "Include nightly builds" %> -
-
-
- <%= radio_button @f, :nightly_builds, false, checked: true, class: "form-check-input nightly-builds-false" %> -
- <%= label :nightly_builds, :false, gettext("No"), class: "radio-text" %> -
-
- <%= radio_button @f, :nightly_builds, true, class: "form-check-input nightly-builds-true", "aria-describedby": "nightly_builds-help-block" %> -
- <%= label :nightly_builds, :true, gettext("Yes"), class: "radio-text" %> -
-
- <%= error_tag @f, :nightly_builds, id: "nightly_builds-help-block", class: "text-danger form-error" %> +
+ <%= label @f, :nightly_builds, gettext("Include nightly builds") %> +
+
+
+ <%= radio_button @f, :nightly_builds, false, checked: true, class: "form-check-input nightly-builds-false" %> +
+ <%= label @f, :nightly_builds_false, gettext("No"), class: "radio-text" %> +
+
+ <%= radio_button @f, :nightly_builds, true, class: "form-check-input nightly-builds-true", "aria-describedby": "nightly_builds-help-block" %> +
+ <%= label @f, :nightly_builds_true, gettext("Yes"), class: "radio-text" %>
-
Select yes if you want to show nightly builds.
+
+ <%= error_tag @f, :nightly_builds, id: "nightly_builds-help-block", class: "text-danger form-error" %>
+
<%= gettext("Select yes if you want to show nightly builds.") %>
+
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_libraries_other.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_libraries_other.html.eex new file mode 100644 index 000000000000..104730d49a27 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_libraries_other.html.eex @@ -0,0 +1,7 @@ +<%= for library_index <- 2..Application.get_env(:block_scout_web, :verification_max_libraries) do %> +
+ <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", index: library_index %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", index: library_index %> +
+<% end %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex index 4f44f6ea0d8a..914518876a72 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex @@ -1,9 +1,10 @@ +<% library_address = "library" <> to_string(@index) <> "_address" |> String.to_atom() %>
-
- <%= label :external_libraries, @library, gettext("Library Address") %> +
+ <%= label :external_libraries, library_address, gettext("Library") <> " " <> to_string(@index) <> " " <> gettext("Address") %>
- <%= text_input :external_libraries, @library_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> + <%= text_input :external_libraries, library_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %>
<%= if assigns[:tooltip_text] do @tooltip_text end %>
-
+
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex new file mode 100644 index 000000000000..adb38c4d7c16 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex @@ -0,0 +1,11 @@ +
+ <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", + index: 1, + tooltip_text: gettext("A library name called in the .sol file. Multiple libraries (up to ") <> to_string(Application.get_env(:block_scout_web, :verification_max_libraries)) <> gettext(") may be added for each contract. Click the Add Library button to add an additional one.") + %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", + index: 1, + tooltip_text: gettext "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." + %> +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex index 3bd50019614f..fc15114a6294 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex @@ -1,9 +1,10 @@ +<% library_name = "library" <> to_string(@index) <> "_name" |> String.to_atom() %>
-
- <%= label :external_libraries, @library, gettext("Library Name") %> -
- <%= text_input :external_libraries, @library_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> -
-
<%= if assigns[:tooltip_text] do @tooltip_text end %>
-
+
+ <%= label :external_libraries, library_name, gettext("Library") <> " " <> to_string(@index) <> " " <> gettext("Name") %> +
+ <%= text_input :external_libraries, library_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> +
+
<%= if assigns[:tooltip_text] do @tooltip_text end %>
+
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex new file mode 100644 index 000000000000..8dd4bdebf564 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex @@ -0,0 +1,21 @@ +
+
+ <%= label @f, :is_yul, gettext("Is Yul contract") %> +
+
+
+ <%= radio_button @f, :is_yul, false, class: "form-check-input autodetectfalse" %> +
+ <%= label @f, :is_yul_false, gettext("No"), class: "radio-text" %> +
+
+ <%= radio_button @f, :is_yul, true, class: "form-check-input autodetecttrue", "aria-describedby": "is_yul-help-block" %> +
+ <%= label @f, :is_yul_true, gettext("Yes"), class: "radio-text" %> +
+
+ <%= error_tag @f, :is_yul, id: "is_yul-help-block", class: "text-danger form-error" %> +
+
<%= gettext("Select Yes if you want to vefify Yul contract.") %>
+
+
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 2bb32c146e99..383aa4584d4c 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 @@ -1,6 +1,6 @@ <% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> <% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %> -<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes.autodetect_constructor_args %> +<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %> <% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> @@ -15,25 +15,29 @@ <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %> - <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_name_field.html", f: f, tooltip: "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." %> + <%= if RustVerifierInterface.enabled?() do %> + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_yul_contracts_switcher.html", f: f %> + <% end %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_name_field.html", f: f, tooltip: gettext "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." %> <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_include_nightly_builds_field.html", f: f %> - <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_compiler_field.html", f: f, compiler_versions: @compiler_versions, tooltip: "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." %> + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_compiler_field.html", f: f, compiler_versions: @compiler_versions, tooltip: gettext "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." %>
- <%= label :evm_version, :evm_version, gettext("EVM Version") %> + <%= label f, :evm_version, gettext("EVM Version") %>
<%= select f, :evm_version, @evm_versions, class: "form-control border-rounded", "aria-describedby": "evm-version-help-block" %>
-
The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. EVM version details.
+
<%= gettext "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." %> <%= gettext "EVM version details" %>.
- <%= label f, "Optimization" %> + <%= label f, :optimization, gettext("Optimization") %>
@@ -49,13 +53,13 @@
<%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger form-error" %>
-
If you enabled optimization during compilation, select yes.
+
<%= gettext "If you enabled optimization during compilation, select yes." %>
">
- <%= label f, :name, gettext("Optimization runs") %> + <%= label f, :optimization_runs, gettext("Optimization runs") %>
<%= text_input f, :optimization_runs, class: "form-control border-rounded", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %>
@@ -70,7 +74,7 @@ <%= textarea f, :contract_source_code, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-source-code-help-block" %> <%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger form-error", "data-test": "contract-source-code-error" %>
-
We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the POA solidity flattener or the truffle flattener.
+
<%= gettext "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" %> <%= gettext "POA solidity flattener or the" %> <%= gettext "truffle flattener" %>.
@@ -79,52 +83,18 @@ <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_constructor_args.html", f: f, display_constructor_arguments_text_area: display_constructor_arguments_text_area %>
- Add Contract Libraries + <%= gettext "Add Contract Libraries" %>

<%= gettext "Contract Libraries" %>

-
- <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", - library: :library1, - library_name: :library1_name, - tooltip_text: "A library name called in the .sol file. Multiple libraries (up to 5) may be added for each contract. Click the Add Library button to add an additional one." - %> - - <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", - library: :library1, - library_address: :library1_address, - tooltip_text: "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." - %> -
- -
- <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", library: :library2, library_name: :library2_name %> + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_first.html" %> - <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", library: :library2, library_address: :library2_address %> -
- -
- <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", library: :library3, library_name: :library3_name %> - - <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", library: :library3, library_address: :library3_address %> -
- -
- <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", library: :library4, library_name: :library4_name %> - - <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", library: :library4, library_address: :library4_address %> -
- -
- <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", library: :library5, library_name: :library5_name %> - - <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", library: :library5, library_address: :library5_address %> -
+ <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_libraries_other.html" %>
- Add Library + <%= gettext "Add Library" %>
@@ -138,7 +108,7 @@ > <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> - <%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation" %> + <%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation", "data-submit-button": "" %> <%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %> <%= link( @@ -150,5 +120,4 @@
<% end %>
- diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex index a5aec2826735..f2bdbffd4344 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex @@ -46,6 +46,4 @@
<% end %>
- - diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex new file mode 100644 index 000000000000..2b2950397e8e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex @@ -0,0 +1,115 @@ +<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %> +
+ <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> + +
+

<%= if RustVerifierInterface.enabled?(), do: gettext "New Solidity/Yul Smart Contract Verification", else: gettext "New Solidity Smart Contract Verification" %>

+ + <%= form_for changeset, + address_contract_verification_path(@conn, :create), + [id: "multi-part-dropzone-form"], + fn f -> %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_include_nightly_builds_field.html", f: f %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_compiler_field.html", f: f, compiler_versions: @compiler_versions, tooltip: gettext "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." %> + +
+
+ <%= label f, :evm_version, gettext("EVM Version") %> +
+ <%= select f, :evm_version, @evm_versions, class: "form-control border-rounded", "aria-describedby": "evm-version-help-block" %> +
+
<%= gettext "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." %> <%= gettext "EVM version details" %>.
+
+
+ +
+
+ <%= label f, :optimization, gettext("Optimization") %> +
+
+
+ <%= radio_button f, :optimization, false, class: "form-check-input optimization-false" %> +
+ <%= label f, :optimization_false, gettext("No"), class: "radio-text" %> +
+
+ <%= radio_button f, :optimization, true, class: "form-check-input optimization-true", "aria-describedby": "optimization-help-block" %> +
+ <%= label f, :optimization_true, gettext("Yes"), class: "radio-text" %> +
+
+ <%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger form-error" %> +
+
<%= gettext "If you enabled optimization during compilation, select yes." %>
+
+
+ +
"> +
+ <%= label f, :optimization_runs, gettext("Optimization runs") %> +
+ <%= text_input f, :optimization_runs, class: "form-control border-rounded", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %> +
+
+
+
+ +
+ +
+
+
+ <%= gettext("Drop sources or click here") %> + <%= error_tag f, :file, id: "file-help-block", class: "text-danger form-error", style: "max-width: 600px;" %> +
+
+
+
<%= if RustVerifierInterface.enabled?(), do: gettext "Drop all Solidity or Yul contract source files into the drop zone.", else: gettext "Drop all Solidity contract source files into the drop zone." %>
+
+ +
+ <%= gettext "Add Contract Libraries" %> +
+ +
+

<%= gettext "Contract Libraries" %>

+ + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_first.html" %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_libraries_other.html" %> + +
+ <%= gettext "Add Library" %> +
+
+ +
+ + <%#= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation", "data-submit-button": "" %> + <%# verify-via-multi-part-files-submit %> + + <%= 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_contract_verification_via_standard_json_input/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex index 31ac5ce76d94..9eb3c96ea58a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex @@ -1,6 +1,6 @@ <% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> <% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %> -<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes.autodetect_constructor_args %> +<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %> <% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> @@ -60,6 +60,4 @@
<% end %>
- - 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 index 2c6fe4d5dee7..4b07896c63a6 100644 --- 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 @@ -44,7 +44,7 @@ > <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> - <%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation" %> + <%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation", "data-submit-button": "" %> <%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %> <%= link( @@ -56,5 +56,4 @@
<% end %>
- diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex index 7ab239233639..18abbbcdfdcd 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex @@ -1,7 +1,7 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> <% contract = last_decompiled_contract_version(@address.decompiled_smart_contracts) %> 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 d89f3e34fbf1..feb70111791c 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 @@ -1,12 +1,12 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> -
+
<%= gettext "More internal transactions have come in" %> 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 90d009f21434..eaa2d7d56fed 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 @@ -1,7 +1,7 @@
"> <% decoded_result = decode(@log, @log.transaction) %> <%= case decoded_result do %> - <%= {:error, :contract_not_verified, _cadidates} -> %> + <% {:error, :contract_not_verified, _cadidates} -> %>
<%= gettext "To see accurate decoded input data, the contract must be verified." %> <%= case @log.transaction do %> 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 d4ef3d3ccbdf..3dc20d3a7f78 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 @@ -1,12 +1,12 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> -
+

<%= gettext "Logs" %>

diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex index 7631094bfb25..58ff9c96f561 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex @@ -2,16 +2,57 @@ <% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> - -
-
- <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> + <%= if @need_wallet do %> +
+ <%= render BlockScoutWeb.SmartContractView, "_connect_container.html" %>
-
+ <% end %> + <%= if @non_custom_abi && assigns[:custom_abi] do %> + + <% else %> + <%= if assigns[:custom_abi] do %> +

<%= gettext "Custom ABI from account" %>

+ <% end %> + <% end %> + <%= + for status <- ["error", "warning", "success", "question"] do + render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status + end + %> + <%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %> + <%= if @non_custom_abi && assigns[:custom_abi] do %> +
+ <% end %> + <%= if @non_custom_abi do %> + +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> +
+
+ <% end %> + <%= if assigns[:custom_abi] do %> + +
" id="custom" role="tabpanel" aria-labelledby="custom-tab" data-smart-contract-functions-custom data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>"> +
+ <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> +
+
+ <% end %> + <%= if @non_custom_abi && assigns[:custom_abi] do %> +
+ <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex index 7631094bfb25..a3577a7cb925 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex @@ -2,7 +2,7 @@ <% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> 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 f42fd4c16726..7691bb3aed86 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 @@ -3,7 +3,7 @@ <%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %> - <% chain_id_for_token_icon = System.get_env("CHAIN_ID") %> + <% chain_id_for_token_icon = Application.get_env(:block_scout_web, :chain_id) %> <% address_hash = @token.contract_address_hash %> <%= render BlockScoutWeb.TokensView, @@ -17,7 +17,7 @@ <%= 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}" + "data-test": "token_transfers_#{@token.contract_address_hash}" ) do %> <%= token_name(@token) %> <% end %> @@ -33,14 +33,14 @@

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

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

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

<% 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 8c682078ac4e..1fd811570f6b 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 @@ -1,7 +1,7 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
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 index 11da76cfc8df..6f922375b973 100644 --- 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 @@ -1,4 +1,4 @@ -<% native_coin = gettext("ETH") %> +<% native_coin = Explorer.coin_name() %> <% wei_value = if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %> <% raw_usd_value = 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 d0c1adf6367f..9b3f6a70f764 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 @@ -47,7 +47,7 @@ placeholder: gettext("Search tokens") ) %>
- <%= if Enum.any?(@token_balances, fn {token_balance, _} -> token_balance.token.type == "ERC-721" end) do %> + <%= if Enum.any?(@token_balances, fn {_token_balance, token} -> token.type == "ERC-721" end) do %> <%= render( "_tokens.html", conn: @conn, @@ -56,7 +56,7 @@ ) %> <% end %> - <%= if Enum.any?(@token_balances, fn {token_balance, _} -> token_balance.token.type == "ERC-1155" end) do %> + <%= if Enum.any?(@token_balances, fn {_token_balance, token} -> token.type == "ERC-1155" end) do %> <%= render( "_tokens.html", conn: @conn, @@ -65,7 +65,7 @@ ) %> <% end %> - <%= if Enum.any?(@token_balances, fn {token_balance, _} -> token_balance.token.type == "ERC-20" end) do %> + <%= if Enum.any?(@token_balances, fn {_token_balance, token} -> token.type == "ERC-20" end) do %> <%= render( "_tokens.html", conn: @conn, diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex index ed696b319a3c..ceaf90505ae2 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex @@ -7,13 +7,13 @@
<% path = cond do - token_balance.token_type == "ERC-721" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token_balance.token.contract_address_hash, to_string(token_balance.token_id)) - token_balance.token_type == "ERC-1155" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token_balance.token.contract_address_hash, to_string(token_balance.token_id)) - true -> token_path(@conn, :show, to_string(token_balance.token.contract_address_hash)) + token_balance.token_type == "ERC-721" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token.contract_address_hash, to_string(token_balance.token_id)) + token_balance.token_type == "ERC-1155" && !is_nil(token_balance.token_id) -> token_instance_path(@conn, :show, token.contract_address_hash, to_string(token_balance.token_id)) + true -> token_path(@conn, :show, to_string(token.contract_address_hash)) end %> <%= link( @@ -22,7 +22,7 @@ ) do %> + <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex index 7e7f0173958d..94f202d1c359 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex @@ -1,12 +1,12 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> -
+
<%= if assigns[:token] do %>

diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index 0bc1b9bce431..a5b57868439b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -2,7 +2,7 @@ <% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex index 143190b9b5dd..e435195fe3b1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex @@ -1,7 +1,7 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex index 7631094bfb25..a1d915d2ec81 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex @@ -2,16 +2,55 @@ <% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> - -
-
- <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> -
+
+ <%= render BlockScoutWeb.SmartContractView, "_connect_container.html" %>
+ <%= if @non_custom_abi && assigns[:custom_abi] do %> + + <% else %> + <%= if assigns[:custom_abi] do %> +

<%= gettext "Custom ABI from account" %>

+ <% end %> + <% end %> + <%= + for status <- ["error", "warning", "success", "question"] do + render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status + end + %> + <%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %> + <%= if @non_custom_abi && assigns[:custom_abi] do %> +
+ <% end %> + <%= if @non_custom_abi do %> + +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> +
+
+ <% end %> + <%= if assigns[:custom_abi] do %> + +
" id="custom" role="tabpanel" aria-labelledby="custom-tab" data-smart-contract-functions-custom data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>"> +
+ <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> +
+
+ <% end %> + <%= if @non_custom_abi && assigns[:custom_abi] do %> +
+ <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex index 7631094bfb25..a3577a7cb925 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex @@ -2,7 +2,7 @@ <% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> - <%= 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, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_model_table.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_model_table.html.eex index 8eb37d09a521..1452cc9e6fe5 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_model_table.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_model_table.html.eex @@ -9,7 +9,7 @@ <%= if details[:type] != "model", do: details.type %> <%= if details[:definition] do %> - <%= if @block.size do %> - <%= Cldr.Unit.new(:byte, @block.size) |> cldr_unit_to_string!() %> + <%= Cldr.Unit.new!(:byte, @block.size) |> cldr_unit_to_string!() %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex index 597eaaea98d1..500e35a86833 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex @@ -113,7 +113,7 @@ text: gettext("Size of the block in bytes.") %> <%= gettext("Size") %> -
<%= if !is_nil(@block.size), do: (Cldr.Unit.new(:byte, @block.size) |> cldr_unit_to_string!()), else: gettext("N/A bytes") %>
+
<%= if !is_nil(@block.size), do: (Cldr.Unit.new!(:byte, @block.size) |> cldr_unit_to_string!()), else: gettext("N/A bytes") %>
@@ -212,7 +212,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Ether") <> " " <> gettext("burned from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %> + text: Explorer.coin_name() <> " " <> gettext("burned from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %> <%= gettext("Burnt Fees") %>
<%= format_wei_value(burned_fee, :ether) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index 5719a2160843..07e77ebefb40 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -38,7 +38,7 @@ <%= if Enum.member?(market_chart_config, :price) do %>
- <%= System.get_env("COIN_NAME") || System.get_env("COIN") %> <%= gettext "Price" %> + <%= Explorer.coin_name() %> <%= gettext "Price" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip_2.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip_2.html.eex index 7efeb4c501bb..87a55cf445cb 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip_2.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip_2.html.eex @@ -7,5 +7,5 @@ data-toggle="tooltip" title="<%= @text %>" > - + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_status_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_status_icon.html.eex index 358c66c8e8b6..bda361e37a18 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_status_icon.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_status_icon.html.eex @@ -1,10 +1,10 @@ <%= case @status do %> <% :success -> %> - + <% {:error, _} -> %> - + <% :awaiting_internal_transactions -> %> - + <% :pending -> %> <% _ -> %> <% end %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_minus.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_minus.html.eex new file mode 100644 index 000000000000..1c08c85c02d0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_minus.html.eex @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_pen.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_pen.html.eex new file mode 100644 index 000000000000..559468d51879 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_pen.html.eex @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_plus.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_plus.html.eex new file mode 100644 index 000000000000..4f93df8c6d56 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_plus.html.eex @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_trash.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_trash.html.eex new file mode 100644 index 000000000000..7d83186dfce9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_trash.html.eex @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex index 0060e01d31c2..d1b5b394bd9f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex @@ -21,21 +21,32 @@
- - +
- +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/error422/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/error422/index.html.eex index bbf314d4bb62..fb188f65886c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/error422/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/error422/index.html.eex @@ -1,12 +1,12 @@ -
+
- Page Not Found + Request cannot be processed
-

Unprocessable Entity

-

The request was well-formed but was unable to be followed due to semantic errors. Maybe, you mistype a hash of tx/block/address?

- Back Home +

<%= gettext "Request cannot be processed" %>

+

<%= gettext "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." %>

+ <%= gettext "Back to home" %>
-
\ No newline at end of file +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_blockchain_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_blockchain_icon.html.eex new file mode 100644 index 000000000000..f8234731c29d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_blockchain_icon.html.eex @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/internal_server_error/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/internal_server_error/index.html.eex new file mode 100644 index 000000000000..df7ff8c5a0f5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/internal_server_error/index.html.eex @@ -0,0 +1,12 @@ +
+
+
+ Internal server error +
+
+

<%= gettext "Internal server error" %>

+

<%= gettext "An unexpected error has occurred. Try reloading the page, or come back soon and try again." %>

+ <%= gettext "Back to home" %> +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex index b7ae6e7f2fd3..d88a6d9c5ccb 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex @@ -21,7 +21,7 @@ - <%= BlockScoutWeb.TransactionView.value(@internal_transaction, include_label: false) %> <%= gettext "Ether" %> + <%= BlockScoutWeb.TransactionView.value(@internal_transaction, include_label: false) %> <%= Explorer.coin_name() %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_account_menu_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_account_menu_item.html.eex new file mode 100644 index 000000000000..e8f8e58f6203 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_account_menu_item.html.eex @@ -0,0 +1,33 @@ +<%= if Explorer.Account.enabled?() do %> + <%= if @current_user do %> + + <% else %> +
  • + + + <%= render BlockScoutWeb.IconsView, "_accounts_icon.html" %> + + Sign in + +
  • + <% end %> +<% end %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex new file mode 100644 index 000000000000..0e777ee945c2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex @@ -0,0 +1,13 @@ + + +<% sub_network = Keyword.get(Application.get_env(:block_scout_web, BlockScoutWeb.Chain), :subnetwork) %> + + +
  • + + <%= gettext("Add") <> " #{sub_network}" %> + +
  • \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex index fe6c84c84814..e63fe8a9456b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex @@ -21,7 +21,7 @@
    <% main_nets = main_nets(other_networks()) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex deleted file mode 100644 index d0419e5b0bae..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex +++ /dev/null @@ -1,53 +0,0 @@ -
    -
    -
    -
    -
    - - - -
    -
    -

    <%= gettext("Change Network") %>

    -

    <%= gettext("Use the search box to find a hosted network, or select from the list of available networks below.") %>

    -
    -
    - - - - -
    -
    -
    <%= gettext("All") %>
    -
    <%= gettext("Mainnet") %>
    -
    <%= gettext("Testnet") %>
    -
    <%= gettext("Favorites") %>
    -
    -
    - <% main_nets = dropdown_main_nets() %> - <% test_nets = dropdown_test_nets() %> -
    - <%= for %{url: url, title: title} <- main_nets do %> - <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %> - <% end %> - <%= for %{url: url, title: title} <- test_nets do %> - <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Testnet" %> - <% end %> -
    -
    - <%= for %{url: url, title: title} <- main_nets do %> - <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %> - <% end %> -
    -
    - <%= for %{url: url, title: title} <- test_nets do %> - <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Testnet" %> - <% end %> -
    -
    -
    No content.
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex deleted file mode 100644 index 74ab4ed7baa2..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex +++ /dev/null @@ -1,25 +0,0 @@ -
    - - -
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_search.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_search.html.eex index 012f71825177..18dbd0331bbf 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_search.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_search.html.eex @@ -6,7 +6,7 @@ id="<%= @id %>" class="main-search-autocomplete" data-test="search_input" - data-chain-id="<%= System.get_env("CHAIN_ID") %>" + data-chain-id="<%= Application.get_env(:block_scout_web, :chain_id) %>" data-display-token-icons="<%= System.get_env("DISPLAY_TOKEN_ICONS") %>" type="text" tabindex="1" diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index bd8a2e8be763..494934049d6e 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -1,4 +1,7 @@ <% apps_menu = Application.get_env(:block_scout_web, :apps_menu) %> +<% other_nets = dropdown_other_nets() %> +<% test_nets = test_nets(dropdown_nets()) %> +<% main_nets = dropdown_head_main_nets() %>
    <%= render BlockScoutWeb.LayoutView, "_search.html", conn: @conn, id: "main-search-autocomplete-mobile", additional_classes: ["mobile-search-show"] %> - \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 42fa7cd61490..60c0754acf5a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -41,11 +41,11 @@ window.localized = { 'Blocks Indexed': '<%= gettext("Blocks Indexed") %>', 'Block Processing': '<%= gettext("Block Mined, awaiting import...") %>', - 'Indexing Tokens': '<%= gettext("Indexing Tokens") %>', + 'Blocks With Internal Transactions Indexed': '<%= gettext("Blocks With Internal Transactions Indexed") %>', 'Less than': '<%= gettext("Less than") %>', 'Market Cap': '<%= gettext("Market Cap") %>', 'Price': '<%= gettext("Price") %>', - 'Ether': '<%= gettext("Ether") %>', + 'Ether': '<%= Explorer.coin_name() %>', 'Tx/day': '<%= gettext("Tx/day") %>' } @@ -53,8 +53,14 @@ @@ -288,5 +304,11 @@ <% end %> + <%= if @view_module in [Elixir.BlockScoutWeb.AddressContractVerificationView, Elixir.BlockScoutWeb.AddressContractVerificationVyperView, Elixir.BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView, Elixir.BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView, Elixir.BlockScoutWeb.AddressContractVerificationViaJsonView, Elixir.BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView] do %> + + <% end %> + <%= if @view_module in [Elixir.BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView, Elixir.BlockScoutWeb.AddressContractVerificationViaJsonView, Elixir.BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView] do %> + + <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex index d91690ea9e66..72d19629de38 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex @@ -1,12 +1,12 @@ -
    +
    - Page Not Found + Page not found
    -

    Page not found

    -

    The requested path was not found on BlockScout.

    - Back Home +

    <%= gettext "Page not found" %>

    +

    <%= gettext "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for." %>

    + <%= gettext "Back to home" %>
    -
    \ No newline at end of file +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex index f27a12dcfc1a..1df5a4d0550f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex @@ -9,7 +9,7 @@ data-address-hash="<%= @result.address_hash %>" > <%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %> - <% chain_id_for_token_icon = System.get_env("CHAIN_ID") %> + <% chain_id_for_token_icon = Application.get_env(:block_scout_web, :chain_id) %> <% address_hash = @result.address_hash %> <%= render BlockScoutWeb.TokensView, diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/results.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/results.html.eex index bb6891c81840..c973f6cccb99 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/search/results.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/results.html.eex @@ -1,7 +1,7 @@
    " + data-chain-id="<%= Application.get_env(:block_scout_web, :chain_id) %>" data-display-token-icons="<%= System.get_env("DISPLAY_TOKEN_ICONS") %>" > <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex index baf0c0ebc0a8..23ec24c306e9 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex @@ -1,6 +1,6 @@ -<% minimal_proxy_template = Chain.get_minimal_proxy_template(@address.hash) %> -<% metadata_for_verification = minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %> -<% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %> +<% minimal_proxy_template = if assigns[:custom_abi], do: nil, else: Chain.get_minimal_proxy_template(@address.hash) %> +<% metadata_for_verification = if assigns[:custom_abi], do: nil, else: minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %> +<% smart_contract_verified = if assigns[:custom_abi], do: false, else: BlockScoutWeb.AddressView.smart_contract_verified?(@address) %> <%= unless smart_contract_verified do %> <%= if metadata_for_verification do %> <%= if minimal_proxy_template do %> @@ -21,9 +21,6 @@ <%= if smart_contract_verified && @address.smart_contract.is_changed_bytecode do %> <%= render BlockScoutWeb.CommonComponentsView, "_changed_bytecode_warning.html" %> <% end %> -<%= if @action == "write" or (@read_functions_required_wallet && @read_functions_required_wallet != []) do %> - <%= render BlockScoutWeb.SmartContractView, "_connect_container.html" %> -<% end %> <%= if @contract_type == "proxy" do %>

    Implementation address:

    <%= link( @@ -33,7 +30,7 @@

    <% end %> <%= for {function, counter} <- Enum.with_index(@read_only_functions ++ @read_functions_required_wallet, 1) do %> -
    +
    >
    <%= counter %>. <%= case function["type"] do %> @@ -49,12 +46,6 @@ <%= if queryable?(function["inputs"]) || writable?(function) || Helper.read_with_wallet_method?(function) do %>
    - <%= - for status <- ["error", "warning", "success", "question"] do - render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status - end - %> - <%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %> <% function_abi = case Jason.encode([function]) do {:ok, abi_string} -> @@ -66,7 +57,7 @@ @contract_abi end end %> -
    " data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, Address.checksum(@address.hash)) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= function_abi %>" data-implementation-abi="<%= function_abi %>" data-chain-id="<%= Explorer.Chain.Cache.NetVersion.get_version() %>"> + " data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, Address.checksum(@address.hash)) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= function_abi %>" data-implementation-abi="<%= function_abi %>" data-chain-id="<%= Explorer.Chain.Cache.NetVersion.get_version() %>" data-custom-abi="<%= if assigns[:custom_abi], do: true, else: false %>"> @@ -101,7 +92,7 @@ <%= if Helper.payable?(function) do %>
    + data-toggle="tooltip" title='Amount in native token <<%= Explorer.coin_name() %>>' class="form-control form-control-sm address-input-sm" placeholder='value(<%= Explorer.coin_name() %>)' min="0" step="1e-18" />
    <% end %> @@ -124,7 +115,7 @@
    <% else %> <%= cond do %> - <%= outputs?(function["outputs"]) -> %> + <% outputs?(function["outputs"]) -> %>
    <% length = Enum.count(function["outputs"]) %> <%= for {output, index} <- Enum.with_index(function["outputs"]) do %> @@ -143,7 +134,7 @@ <%= gettext("WEI")%> - <%= gettext("ETH")%> + <%= Explorer.coin_name() %>
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex index 0e16239094eb..14a749e48a18 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex @@ -7,7 +7,7 @@ <%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %> - <% chain_id_for_token_icon = System.get_env("CHAIN_ID") %> + <% chain_id_for_token_icon = Application.get_env(:block_scout_web, :chain_id) %> <% foreign_token_contract_address_hash = nil %> <% token_hash_for_token_icon = if foreign_token_contract_address_hash, do: foreign_token_contract_address_hash, else: Address.checksum(@token.contract_address_hash) %> <%= diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/index.html.eex index fde314766433..99bb26342d7a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/index.html.eex @@ -4,6 +4,7 @@ "_details.html", token: @token, counters_path: @counters_path, + tags: @tags, conn: @conn ) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex index 5d2aaef94ab4..9d78dcc4e043 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex @@ -4,6 +4,7 @@ "_details.html", token: @token, counters_path: @counters_path, + tags: @tags, conn: @conn ) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex index 85ee9978aff4..49fc7ae82d77 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex @@ -1,7 +1,7 @@
    " + data-chain-id="<%= Application.get_env(:block_scout_web, :chain_id) %>" data-display-token-icons="<%= System.get_env("DISPLAY_TOKEN_ICONS") %>" > <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex index 7b387b828272..bb2b4d71ca21 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex @@ -62,7 +62,7 @@
    <%= if media_type(media_src(@token_instance.instance, true)) == "video" do %> -
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex index 284498494291..c99a8f1036bf 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex @@ -25,4 +25,10 @@ class: "card-tab #{tab_status("raw-trace", @conn.request_path)}", to: AccessHelpers.get_path(@conn, :transaction_raw_trace_path, :index, @transaction) ) %> + <%= link( + gettext("State changes"), + class: "card-tab #{tab_status("state", @conn.request_path)}", + to: AccessHelpers.get_path(@conn, :transaction_state_path, :index, @transaction) + ) + %>
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex index a8faa835bfcd..f555c379681b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex @@ -1,5 +1,7 @@ <% status = transaction_status(@transaction) %> <% error_in_internal_tx = @transaction.has_error_in_internal_txs %> +<% current_user = AuthController.current_user(@conn) %> +<% tx_tags = BlockScoutWeb.Models.GetTransactionTags.get_transaction_with_addresses_tags(@transaction, current_user) %>
    @@ -31,6 +33,10 @@ <%= if method_name do %> <%= render BlockScoutWeb.FormView, "_tag.html", text: method_name, additional_classes: ["method", "ml-1"] %> <% end %> + <%= if tx_tags.personal_tx_tag && tx_tags.personal_tx_tag.name !== :error do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: tx_tags.personal_tx_tag.name, additional_classes: [tag_name_to_label(tx_tags.personal_tx_tag.name), "ml-1"] %> + <% end %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: tx_tags %>
    @@ -40,7 +46,7 @@ - <%= value(@transaction, include_label: false) %> <%= gettext "Ether" %> + <%= value(@transaction, include_label: false) %> <%= Explorer.coin_name() %> <%= formatted_fee(@transaction, denomination: :ether, include_label: false) %> <%= gettext "TX Fee" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex index fd9efa4435af..4896e6e85312 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex @@ -1,13 +1,13 @@ <%= case token_transfer_amount(@transfer) do %> <% {:ok, :erc721_instance} -> %> - <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> + <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: List.first(@transfer.token_ids) %> <% {:ok, :erc1155_instance, value} -> %> <% transfer_type = Chain.get_token_transfer_type(@transfer) %> <%= if transfer_type == :token_spawning do %> - <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> + <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: List.first(@transfer.token_ids) %> <% else %> <%= "#{value} " %> - <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> + <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: List.first(@transfer.token_ids) %> <% end %> <% {:ok, :erc1155_instance, values, token_ids, _decimals} -> %> <% values_ids = Enum.zip(values, token_ids) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex index 24e903669844..d26557b9c32a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex @@ -1,45 +1,48 @@ <%= with {:ok, from_address} <- Chain.hash_to_address(@transfer.from_address_hash), - {:ok, to_address} <- Chain.hash_to_address(@transfer.to_address_hash) do %> +{:ok, to_address} <- Chain.hash_to_address(@transfer.to_address_hash) do %> +<% from_tags = BlockScoutWeb.Models.GetAddressTags.get_address_tags(@transfer.from_address_hash, @current_user) %> +<% to_tags = BlockScoutWeb.Models.GetAddressTags.get_address_tags(@transfer.to_address_hash, @current_user) %> - - From - - - <%= render BlockScoutWeb.AddressView, "_link.html", address: from_address, contract: BlockScoutWeb.AddressView.contract?(from_address), use_custom_tooltip: false, trimmed: false %> - - - <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy_for_table.html", - additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders", "btn-copy-token-transfer"], - clipboard_text: from_address, - aria_label: gettext("Copy From Address"), - title: gettext("Copy From Address"), - style: "position: relative;" %> - + + From + + + <%= render BlockScoutWeb.AddressView, "_link.html", address: from_address, contract: BlockScoutWeb.AddressView.contract?(from_address), use_custom_tooltip: false, trimmed: false %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: from_tags %> + + + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy_for_table.html", +additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders", "btn-copy-token-transfer"], +clipboard_text: from_address, +aria_label: gettext("Copy From Address"), +title: gettext("Copy From Address"), +style: "position: relative;" %> + - - To - - - <%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: BlockScoutWeb.AddressView.contract?(to_address), use_custom_tooltip: false, trimmed: false %> - - - - <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy_for_table.html", - additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders", "btn-copy-token-transfer"], - clipboard_text: to_address, - aria_label: gettext("Copy To Address"), - title: gettext("Copy To Address"), - style: "position: relative;"%> - + + To + + + <%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: BlockScoutWeb.AddressView.contract?(to_address), use_custom_tooltip: false, trimmed: false %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: to_tags %> + + + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy_for_table.html", +additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders", "btn-copy-token-transfer"], +clipboard_text: to_address, +aria_label: gettext("Copy To Address"), +title: gettext("Copy To Address"), +style: "position: relative;"%> + - - For - - <% end %> - - <%= render BlockScoutWeb.TransactionView, "_total_transfers.html", transfer: @transfer %> - + + For + +<% end %> + + <%= render BlockScoutWeb.TransactionView, "_total_transfers.html", transfer: @transfer %> + - \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/invalid.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/invalid.html.eex deleted file mode 100644 index 8ec41880a7ef..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/invalid.html.eex +++ /dev/null @@ -1,14 +0,0 @@ -
    -
    -
    -
    -
    -

    <%= gettext "Invalid Transaction Hash" %>

    -
    - <%= @transaction_hash %> <%= gettext "is not a valid transaction hash" %> -
    -
    -
    -
    -
    -
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/not_found.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/not_found.html.eex index 97f721ec05da..440206247735 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/not_found.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/not_found.html.eex @@ -1,34 +1,16 @@ -
    +
    - Block Not Found + Transaction not found
    -
    -

    <%= gettext("Sorry, We are unable to locate this transaction Hash") %>

    -
    -
    -
    - 1 -

    <%= gettext("If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page.") %>

    -
    -
    - 2 -

    <%= gettext("It could still be in the TX Pool of a different node, waiting to be broadcasted.") %>

    -
    -
    -
    -
    - 3 -

    <%= gettext("During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it.") %>

    -
    -
    - 4 -

    <%= gettext("If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information.") %>

    -
    -
    -
    - <%= gettext("Back Home") %> +

    <%= gettext("Sorry, we are unable to locate this transaction hash") %>

    +
    +

    <%= gettext("1. If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page.") %>

    +

    <%= gettext("2. It could still be in the TX Pool of a different node, waiting to be broadcasted.") %>

    +

    <%= gettext("3. During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it.") %>

    +

    <%= gettext("4. If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information.") %>

    + <%= gettext("Back to home") %>
    -
    \ No newline at end of file +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index 760fdafe5368..cefcc6c94e75 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -1,5 +1,6 @@ <% block = @transaction.block %> <% from_address_hash = @transaction.from_address_hash %> +<% from_address = @transaction.from_address %> <% to_address_hash = @transaction.to_address_hash %> <% created_address_hash = @transaction.created_contract_address_hash %> <% type = if @transaction.type == 2, do: "2 (EIP-1559)", else: @transaction.type %> @@ -47,6 +48,11 @@

    <%= gettext "Transaction Details" %> + <% personal_tx_tag = if assigns[:tx_tags], do: @tx_tags.personal_tx_tag, else: nil %> + <%= if personal_tx_tag && personal_tx_tag.name !== :error do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tx_tag.name, additional_classes: [tag_name_to_label(personal_tx_tag.name), "ml-1"] %> + <% end %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: @tx_tags %>

    <%= if status == :pending do %> @@ -193,13 +199,11 @@ text: gettext("Address (external or contract) sending the transaction.") %> <%= gettext "From" %>
    - <%= link( - from_address_hash, - to: address_path(@conn, :show, from_address_hash) - ) %> + <%= render BlockScoutWeb.AddressView, "_link.html", address: from_address, contract: BlockScoutWeb.AddressView.contract?(from_address), use_custom_tooltip: false, trimmed: false %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: @from_tags %> <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], - clipboard_text: from_address_hash, + clipboard_text: Address.checksum(from_address_hash), aria_label: gettext("Copy From Address"), title: gettext("Copy From Address") %>
    @@ -221,24 +225,20 @@ <%= cond do %> <% created_address_hash -> %> [<%= gettext("Contract") %>  - <%= link( - recipient_address_hash, - to: address_path(@conn, :show, recipient_address_hash) - ) %> + <%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: BlockScoutWeb.AddressView.contract?(to_address), use_custom_tooltip: false, trimmed: false %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: @to_tags %>  <%= gettext("created") %>] <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], - clipboard_text: recipient_address_hash, + clipboard_text: Address.checksum(recipient_address_hash), aria_label: gettext("Copy To Address"), title: gettext("Copy To Address") %> <% recipient_address_hash -> %> - <%= link( - recipient_address_hash, - to: address_path(@conn, :show, recipient_address_hash) - ) %> + <%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: BlockScoutWeb.AddressView.contract?(to_address), use_custom_tooltip: false, trimmed: false %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: @to_tags %> <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], - clipboard_text: recipient_address_hash, + clipboard_text: Address.checksum(recipient_address_hash), aria_label: gettext("Copy To Address"), title: gettext("Copy To Address") %> <% true -> %> @@ -408,7 +408,7 @@
    <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Amount of") <> " " <> gettext("Ether") <> " " <> gettext("burned for this transaction. Equals Block Base Fee per Gas * Gas Used.") %> + text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burned for this transaction. Equals Block Base Fee per Gas * Gas Used.") %> <%= gettext "Transaction Burnt Fee" %>
    <%= format_wei_value(burned_fee, :ether) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex index 2b1b4e0d35a0..e64f3bdbf6df 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex @@ -1,7 +1,7 @@
    <% decoded_result = decode(@log, @transaction) %> <%= case decoded_result do %> - <%= {:error, :contract_not_verified, _cadidates} -> %> + <% {:error, :contract_not_verified, _cadidates} -> %>
    <%= gettext "To see accurate decoded input data, the contract must be verified." %> <%= case @log do %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex index 0c0f26540587..8bb5c03b4969 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex @@ -5,26 +5,22 @@ <%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %>

    <%= gettext "Raw Trace" %> - - - - - + <%= if Enum.count(@internal_transactions) > 0 do %> + <% raw_trace_text = for {line, _} <- raw_traces_with_lines(@internal_transactions), do: line %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + id: "tx-raw-trace-input", + additional_classes: ["tx-raw-input", "transaction-input"], + clipboard_text: raw_trace_text, + aria_label: gettext("Copy Value"), + title: gettext("Copy Raw Trace"), + style: "float: right;" %> + <% end %>

    <%= if Enum.count(@internal_transactions) > 0 do %>
    <%= for {line, number} <- raw_traces_with_lines(@internal_transactions) do %>
    <%= line %>
    <% end %>
    <% else %>
    - No trace entries found. + <%= gettext "No trace entries found." %>
    <% end %>
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_metatags.html.eex new file mode 100644 index 000000000000..85c3d6675f4a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.TransactionView, "_metatags.html", conn: @conn, transaction: @transaction %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex new file mode 100644 index 000000000000..c2655c48ecf7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex @@ -0,0 +1,66 @@ +<% coin_or_transfer = if @coin_or_token_transfers == :coin, do: :coin, else: elem(List.first(@coin_or_token_transfers), 1)%> +<%= if coin_or_transfer != :coin and coin_or_transfer.token.type != "ERC-20" or has_diff?(@balance_diff) do %> + + <%= if @address.hash == @burn_address_hash do %> + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Address used in token mintings and burnings.") %> + <%= gettext("Burn address") %> +
    + + + <%= render BlockScoutWeb.AddressView, "_link.html", address: @address, contract: BlockScoutWeb.AddressView.contract?(@address), use_custom_tooltip: false %> + + + + <% else %> + <%= if Map.get(assigns, :miner) do %> + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("A block producer who successfully included the block onto the blockchain.") %> + <%= gettext("Miner") %> +
    + + + <%= render BlockScoutWeb.AddressView, "_link.html", address: @address, contract: false, use_custom_tooltip: false %> + + <% else %> + + + <%= render BlockScoutWeb.AddressView, "_link.html", address: @address, contract: BlockScoutWeb.AddressView.contract?(@address), use_custom_tooltip: false %> + + <% end %> + <%= if not_negative?(@balance_before) and not_negative?(@balance_after) do %> + + <%= display_value(@balance_before, coin_or_transfer) %> + + + <%= display_value(@balance_after, coin_or_transfer) %> + + <% else %> + + + <% end %> + <% end %> + + <%= if is_list(@coin_or_token_transfers) and elem(List.first(@coin_or_token_transfers), 1).token.type != "ERC-20" do %> + <%= for {type, transfer} <- @coin_or_token_transfers do %> + <%= case type do %> + <% :from -> %> +
    â–ŧ <%= display_nft(transfer) %>
    + <% :to -> %> +
    ▲ <%= display_nft(transfer) %>
    + <% end %> + <% end %> + <% else %> + <%= if not_negative?(@balance_diff) do %> + ▲ <%= display_value(@balance_diff, coin_or_transfer) %> + <% else %> + â–ŧ <%= display_value(absolute_value_of(@balance_diff), coin_or_transfer) %> + <% end %> + <% end %> + + +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_token_balance.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_token_balance.html.eex new file mode 100644 index 000000000000..6803a22d4087 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_token_balance.html.eex @@ -0,0 +1 @@ +<%= format_according_to_decimals(@balance, @transfer.token.decimals) %><%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/index.html.eex new file mode 100644 index 000000000000..c62dbb18bdf5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/index.html.eex @@ -0,0 +1,51 @@ +
    + <%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> +
    + <%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %> +
    +

    <%= gettext "State changes" %>

    + + <%= cond do %> + <% Chain.transaction_to_status(@transaction) == :pending -> %> +
    + <%= gettext "The changes from this transaction have not yet happened since the transaction is still pending." %> +
    + <% not has_state_changes?(@transaction) -> %> +
    + <%= gettext "This transaction hasn't changed state." %> +
    + <% true -> %> +
    +
    + + + + + + + + + + + + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 5 %> + +
    +
     
    +
    +
    <%= gettext "Address" %>
    +
    +
    <%= gettext "Balance before" %>
    +
    +
    <%= gettext "Balance after" %>
    +
    +
    <%= gettext "Change" %>
    +
    +
    +
    + <% end %> +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_contract.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_contract.html.eex new file mode 100644 index 000000000000..dbd64714d6a3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_contract.html.eex @@ -0,0 +1,61 @@ + + + + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: @contract.address, + contract: true, + use_custom_tooltip: false + %> + + + + <%= balance(@contract.address) %> + + + + + <%= if @contract.address.transactions_count do %> + <%= Number.Delimit.number_to_delimited(@contract.address.transactions_count, precision: 0) %> + <% else %> + <%= gettext "N/A" %> + <% end %> + + + + + + <%= if @contract.is_vyper_contract, do: gettext("Vyper"), else: gettext("Solidity") %> + + + + + <%= @contract.compiler_version %> + + + + + <%= if @contract.optimization do %> + + <% else %> + + <% end %> + + + + <%= if @contract.constructor_arguments do %> + + <% else %> + + <% end %> + + + + + + + + <% market_cap_usd = if @token && @token.market_cap_usd, do: @token.market_cap_usd, else: gettext("N/A") %> + <%= market_cap_usd %> + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_metatags.html.eex new file mode 100644 index 000000000000..88e38e1c7c58 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_metatags.html.eex @@ -0,0 +1,8 @@ + + <%= gettext( + "Verified contracts - %{subnetwork} Explorer", + subnetwork: BlockScoutWeb.LayoutView.subnetwork_title() + ) %> + +"> +"> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_stats.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_stats.html.eex new file mode 100644 index 000000000000..d69ebe0ec188 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/_stats.html.eex @@ -0,0 +1,33 @@ +
    +
    +
    +
    +

    <%= gettext "Contracts" %>

    +
    +
    +

    <%= BlockScoutWeb.Cldr.Number.to_string!(@contracts_count, format: "#,###") %>

    + <%= gettext ("Total") %> +
    +
    +

    <%= if 0 |> Decimal.new() |> Decimal.lt?(@new_contracts_count), do: "+" %><%= BlockScoutWeb.Cldr.Number.to_string!(@new_contracts_count, format: "#,###") %>

    + <%= gettext ("Last 24h") %> +
    +
    +
    +
    +

    <%= gettext "Verified Contracts" %>

    +
    +
    +

    <%= BlockScoutWeb.Cldr.Number.to_string!(@verified_contracts_count, format: "#,###") %>

    + <%= gettext ("Total") %> +
    +
    +

    <%= if 0 |> Decimal.new() |> Decimal.lt?(@new_verified_contracts_count), do: "+" %><%= BlockScoutWeb.Cldr.Number.to_string!(@new_verified_contracts_count, format: "#,###") %>

    + <%= gettext ("Last 24h") %> +
    +
    +
    + +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/index.html.eex new file mode 100644 index 000000000000..b8f801b6993b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/verified_contracts/index.html.eex @@ -0,0 +1,101 @@ +<%= render "_stats.html", assigns %> +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +

    <%= gettext "Verified Contracts" %>

    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: @page_number, show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + + + +
    + " placeholder="<%= gettext "Contract name or address" %>" id="search-text-input"> +
    + + + +
    +
    + + + + + + + + + + + + + + + + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 9 %> + +
    +
    <%= gettext "Address" %>
    +
    +
    <%= gettext "Balance" %>
    +
    +
    <%= gettext "Txns" %>
    +
    +
    <%= gettext "Compiler" %>
    +
    +
    <%= gettext "Version" %>
    +
    +
    <%= gettext "Optimization" %>
    +
    +
    <%= gettext "Constructor args" %>
    +
    +
    <%= gettext "Verified" %>
    +
    +
    <%= gettext "Market cap" %>
    +
    +
    +
    + +
    +
    +
    + + <%= gettext "There are no verified contracts." %> + +
    +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: @page_number, 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/visualize_sol2uml/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/visualize_sol2uml/index.html.eex new file mode 100644 index 000000000000..b78d3ef514ec --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/visualize_sol2uml/index.html.eex @@ -0,0 +1,36 @@ +
    +
    +
    +
    +
    <%= gettext("UML diagram") %>
    +

    + <%= gettext("For contract") %> + <%= link to: address_contract_path(@conn, :index, @address), "data-test": "address_hash_link" do %> + <%= render( + BlockScoutWeb.AddressView, + "_responsive_hash.html", + address: @address, + contract: true, + use_custom_tooltip: false + ) %> + <% end %> +

    +
    +
    + + + + + +
    + + + +
    +
    +
    +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex b/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex index 0f09ce2302fc..77f4fa3e0599 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex @@ -27,6 +27,19 @@ defmodule BlockScoutWeb.ABIEncodedValueView do :error end + def value_json(type, value) do + decoded_type = FunctionSelector.decode_type(type) + + do_value_json(decoded_type, value) + rescue + exception -> + Logger.warn(fn -> + ["Error determining value json for #{inspect(type)}: ", Exception.format(:error, exception)] + end) + + nil + end + def copy_text(type, value) do decoded_type = FunctionSelector.decode_type(type) @@ -145,5 +158,56 @@ defmodule BlockScoutWeb.ABIEncodedValueView do defp base_value_html(_, value, _no_links), do: HTML.html_escape(value) + defp do_value_json({:bytes, _}, value) do + do_value_json(:bytes, value) + end + + defp do_value_json({:array, type, _}, value) do + do_value_json({:array, type}, value) + end + + defp do_value_json({:array, type}, value) do + values = + Enum.map(value, fn inner_value -> + do_value_json(type, inner_value) + end) + + values + end + + defp do_value_json({:tuple, types}, values) do + values_list = + values + |> Tuple.to_list() + |> Enum.with_index() + |> Enum.map(fn {value, i} -> + do_value_json(Enum.at(types, i), value) + end) + + values_list + end + + defp do_value_json(type, value) do + base_value_json(type, value) + end + + defp base_value_json(_, {:dynamic, value}) do + hex(value) + end + + defp base_value_json(:address, value) do + hex(value) + end + + defp base_value_json(:address_text, value) do + hex(value) + end + + defp base_value_json(:bytes, value) do + hex(value) + end + + defp base_value_json(_, value), do: value + defp hex(value), do: "0x" <> Base.encode16(value, case: :lower) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/access_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/access_helpers.ex index c89ac104353d..ad89312b5f4f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/access_helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/access_helpers.ex @@ -8,34 +8,14 @@ defmodule BlockScoutWeb.AccessHelpers do alias BlockScoutWeb.API.APILogger alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.WebRouter.Helpers + alias Explorer.AccessHelpers + alias Explorer.Account.Api.Key, as: ApiKey alias Plug.Conn alias RemoteIp def restricted_access?(address_hash, params) do - restricted_list_var = Application.get_env(:block_scout_web, :restricted_list) - restricted_list = (restricted_list_var && String.split(restricted_list_var, ",")) || [] - - if Enum.count(restricted_list) > 0 do - formatted_restricted_list = - restricted_list - |> Enum.map(fn addr -> - String.downcase(addr) - end) - - formatted_address_hash = String.downcase(address_hash) - - address_restricted = - formatted_restricted_list - |> Enum.member?(formatted_address_hash) - - key = if params && Map.has_key?(params, "key"), do: Map.get(params, "key"), else: nil - correct_key = key && key == Application.get_env(:block_scout_web, :restricted_list_key) - - if address_restricted && !correct_key, do: {:restricted_access, true}, else: {:ok, false} - else - {:ok, false} - end + AccessHelpers.restricted_access?(address_hash, params) end def get_path(conn, path, template, address_hash) do @@ -81,11 +61,17 @@ defmodule BlockScoutWeb.AccessHelpers do ip = remote_ip_from_headers || remote_ip ip_string = to_string(:inet_parse.ntoa(ip)) + plan = get_plan(conn.query_params) + cond do - conn.query_params && Map.has_key?(conn.query_params, "apikey") && - Map.get(conn.query_params, "apikey") == static_api_key -> + check_api_key(conn) && get_api_key(conn) == static_api_key -> rate_limit_by_key(static_api_key, api_rate_limit_by_key) + check_api_key(conn) && !is_nil(plan) -> + conn + |> get_api_key() + |> rate_limit_by_key(plan.max_req_per_second) + Enum.member?(api_rate_limit_whitelisted_ips(), ip_string) -> rate_limit_by_ip(ip_string, api_rate_limit_by_ip) @@ -95,6 +81,26 @@ defmodule BlockScoutWeb.AccessHelpers do end end + defp check_api_key(conn) do + conn.query_params && Map.has_key?(conn.query_params, "apikey") + end + + defp get_api_key(conn) do + Map.get(conn.query_params, "apikey") + end + + defp get_plan(query_params) do + with true <- query_params && Map.has_key?(query_params, "apikey"), + api_key_value <- Map.get(query_params, "apikey"), + api_key <- ApiKey.api_key_with_plan_by_value(api_key_value), + false <- is_nil(api_key) do + api_key.identity.plan + else + _ -> + nil + end + end + defp rate_limit_by_key(api_key, api_rate_limit_by_key) do case Hammer.check_rate("api-#{api_key}", 1_000, api_rate_limit_by_key) do {:allow, _count} -> diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex new file mode 100644 index 000000000000..0e3a65e61653 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex @@ -0,0 +1,7 @@ +defmodule BlockScoutWeb.Account.Api.V1.AccountView do + def render("message.json", %{message: message}) do + %{ + "message" => message + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex new file mode 100644 index 000000000000..d97e35f9145b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex @@ -0,0 +1,27 @@ +defmodule BlockScoutWeb.Account.Api.V1.TagsView do + def render("address_tags.json", %{tags_map: tags_map}) do + tags_map + end + + def render("transaction_tags.json", %{ + tags_map: %{ + personal_tags: personal_tags, + watchlist_names: watchlist_names, + personal_tx_tag: personal_tx_tag, + common_tags: common_tags + } + }) do + %{ + personal_tx_tag: prepare_transaction_tag(personal_tx_tag), + personal_tags: personal_tags, + watchlist_names: watchlist_names, + common_tags: common_tags + } + end + + def prepare_transaction_tag(nil), do: nil + + def prepare_transaction_tag(transaction_tag) do + %{"label" => transaction_tag.name} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex new file mode 100644 index 000000000000..b2761714ae90 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex @@ -0,0 +1,169 @@ +defmodule BlockScoutWeb.Account.Api.V1.UserView do + alias BlockScoutWeb.Account.Api.V1.AccountView + alias BlockScoutWeb.API.V2.Helper + alias Ecto.Changeset + alias Explorer.Chain + + def render("message.json", assigns) do + AccountView.render("message.json", assigns) + end + + def render("user_info.json", %{identity: identity}) do + %{"name" => identity.name, "email" => identity.email, "avatar" => identity.avatar, "nickname" => identity.nickname} + end + + def render("watchlist_addresses.json", %{watchlist_addresses: watchlist_addresses, exchange_rate: exchange_rate}) do + Enum.map(watchlist_addresses, &prepare_watchlist_address(&1, exchange_rate)) + end + + def render("watchlist_address.json", %{watchlist_address: watchlist_address, exchange_rate: exchange_rate}) do + prepare_watchlist_address(watchlist_address, exchange_rate) + end + + def render("address_tags.json", %{address_tags: address_tags}) do + Enum.map(address_tags, &prepare_address_tag/1) + end + + def render("address_tag.json", %{address_tag: address_tag}) do + prepare_address_tag(address_tag) + end + + def render("transaction_tags.json", %{transaction_tags: transaction_tags}) do + Enum.map(transaction_tags, &prepare_transaction_tag/1) + end + + def render("transaction_tag.json", %{transaction_tag: transaction_tag}) do + prepare_transaction_tag(transaction_tag) + end + + def render("api_keys.json", %{api_keys: api_keys}) do + Enum.map(api_keys, &prepare_api_key/1) + end + + def render("api_key.json", %{api_key: api_key}) do + prepare_api_key(api_key) + end + + def render("custom_abis.json", %{custom_abis: custom_abis}) do + Enum.map(custom_abis, &prepare_custom_abi/1) + end + + def render("custom_abi.json", %{custom_abi: custom_abi}) do + prepare_custom_abi(custom_abi) + end + + def render("public_tags_requests.json", %{public_tags_requests: public_tags_requests}) do + Enum.map(public_tags_requests, &prepare_public_tags_request/1) + end + + def render("public_tags_request.json", %{public_tags_request: public_tags_request}) do + prepare_public_tags_request(public_tags_request) + end + + def render("changeset_errors.json", %{changeset: changeset}) do + %{ + "errors" => + Changeset.traverse_errors(changeset, fn {msg, opts} -> + Regex.replace(~r"%{(\w+)}", msg, fn _, key -> + opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() + end) + end) + } + end + + def prepare_watchlist_address(watchlist, exchange_rate) do + address = get_address(watchlist.address_hash) + + %{ + "id" => watchlist.id, + "address" => Helper.address_with_info(nil, address, watchlist.address_hash), + "address_hash" => watchlist.address_hash, + "name" => watchlist.name, + "address_balance" => if(address && address.fetched_coin_balance, do: address.fetched_coin_balance.value), + "exchange_rate" => exchange_rate.usd_value, + "notification_settings" => %{ + "native" => %{ + "incoming" => watchlist.watch_coin_input, + "outcoming" => watchlist.watch_coin_output + }, + "ERC-20" => %{ + "incoming" => watchlist.watch_erc_20_input, + "outcoming" => watchlist.watch_erc_20_output + }, + "ERC-721" => %{ + "incoming" => watchlist.watch_erc_721_input, + "outcoming" => watchlist.watch_erc_721_output + } + # , + # "ERC-1155" => %{ + # "incoming" => watchlist.watch_erc_1155_input, + # "outcoming" => watchlist.watch_erc_1155_output + # } + }, + "notification_methods" => %{ + "email" => watchlist.notify_email + } + } + end + + def prepare_custom_abi(custom_abi) do + address = get_address(custom_abi.address_hash) + + %{ + "id" => custom_abi.id, + "contract_address_hash" => custom_abi.address_hash, + "contract_address" => Helper.address_with_info(nil, address, custom_abi.address_hash), + "name" => custom_abi.name, + "abi" => custom_abi.abi + } + end + + def prepare_api_key(api_key) do + %{"api_key" => api_key.value, "name" => api_key.name} + end + + def prepare_address_tag(address_tag) do + address = get_address(address_tag.address_hash) + + %{ + "id" => address_tag.id, + "address_hash" => address_tag.address_hash, + "address" => Helper.address_with_info(nil, address, address_tag.address_hash), + "name" => address_tag.name + } + end + + def prepare_transaction_tag(nil), do: nil + + def prepare_transaction_tag(transaction_tag) do + %{"id" => transaction_tag.id, "transaction_hash" => transaction_tag.tx_hash, "name" => transaction_tag.name} + end + + def prepare_public_tags_request(public_tags_request) do + addresses = + Enum.map(public_tags_request.addresses, fn address_hash -> + Helper.address_with_info(nil, get_address(address_hash), address_hash) + end) + + %{ + "id" => public_tags_request.id, + "full_name" => public_tags_request.full_name, + "email" => public_tags_request.email, + "company" => public_tags_request.company, + "website" => public_tags_request.website, + "tags" => public_tags_request.tags, + "addresses" => public_tags_request.addresses, + "addresses_with_info" => addresses, + "additional_comment" => public_tags_request.additional_comment, + "is_owner" => public_tags_request.is_owner, + "submission_date" => public_tags_request.inserted_at + } + end + + defp get_address(address_hash) do + case Chain.hash_to_address(address_hash, [necessity_by_association: %{:smart_contract => :optional}], false) do + {:ok, address} -> address + _ -> nil + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api_key_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api_key_view.ex new file mode 100644 index 000000000000..a0b21b79da2d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api_key_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.Account.ApiKeyView do + use BlockScoutWeb, :view + + alias Explorer.Account.Api.Key +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/auth_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/auth_view.ex new file mode 100644 index 000000000000..cfbeb001e0fb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/auth_view.ex @@ -0,0 +1,3 @@ +defmodule BlockScoutWeb.Account.AuthView do + use BlockScoutWeb, :view +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/common_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/common_view.ex new file mode 100644 index 000000000000..e296ca90d696 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/common_view.ex @@ -0,0 +1,11 @@ +defmodule BlockScoutWeb.Account.CommonView do + use BlockScoutWeb, :view + + def nav_class(active_item, item) do + if active_item == item do + "dropdown-item active fs-14" + else + "dropdown-item fs-14" + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/custom_abi_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/custom_abi_view.ex new file mode 100644 index 000000000000..3b38effa417b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/custom_abi_view.ex @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.Account.CustomABIView do + use BlockScoutWeb, :view + + alias Ecto.Changeset + alias Explorer.Account.CustomABI + + def format_abi(custom_abi) do + with {_type, abi} <- Changeset.fetch_field(custom_abi, :abi), + false <- is_nil(abi), + {:binary, false} <- {:binary, is_binary(abi)}, + {:ok, encoded_abi} <- Poison.encode(abi) do + encoded_abi + else + {:binary, true} -> + {_type, abi} = Changeset.fetch_field(custom_abi, :abi) + abi + + _ -> + "" + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/public_tags_request_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/public_tags_request_view.ex new file mode 100644 index 000000000000..2a13dd8d7ea3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/public_tags_request_view.ex @@ -0,0 +1,70 @@ +defmodule BlockScoutWeb.Account.PublicTagsRequestView do + use BlockScoutWeb, :view + use Phoenix.HTML + + alias Explorer.Account.PublicTagsRequest + alias Phoenix.HTML.Form + + def array_input(form, field, attrs \\ []) do + values = Form.input_value(form, field) || [""] + id = Form.input_id(form, field) + + content_tag :ul, + id: container_id(id), + data: [index: Enum.count(values), multiple_input_field_container: ""], + class: "multiple-input-fields-container" do + values + |> Enum.map(fn v -> + form_elements(form, field, to_string(v), attrs) + end) + end + end + + def array_add_button(form, field, attrs \\ []) do + id = Form.input_id(form, field) + + content = + form + |> form_elements(field, "", attrs) + |> safe_to_string + + data = [ + prototype: content, + container: container_id(id) + ] + + content_tag(:button, render(BlockScoutWeb.CommonComponentsView, "_svg_plus.html"), + data: data, + class: "add-form-field" + ) + end + + defp form_elements(form, field, k, attrs) do + type = Form.input_type(form, field) + id = Form.input_id(form, field) + + input_opts = + [ + name: new_field_name(form, field), + value: k, + id: id, + class: "form-control public-tags-address" + ] ++ attrs + + content_tag :li, class: "public-tags-address form-group" do + [ + apply(Form, type, [form, field, input_opts]), + content_tag(:button, render(BlockScoutWeb.CommonComponentsView, "_svg_minus.html"), + data: [container: container_id(id)], + class: "remove-form-field ml-1" + ) + ] + end + end + + defp container_id(id), do: id <> "_container" + + defp new_field_name(form, field) do + Form.input_name(form, field) <> "[]" + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/tag_address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/tag_address_view.ex new file mode 100644 index 000000000000..74886c33e9e5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/tag_address_view.ex @@ -0,0 +1,7 @@ +defmodule BlockScoutWeb.Account.TagAddressView do + use BlockScoutWeb, :view + + import BlockScoutWeb.AddressView, only: [trimmed_hash: 1] + + alias Explorer.Account.TagAddress +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/tag_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/tag_transaction_view.ex new file mode 100644 index 000000000000..7edfa1e3403e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/tag_transaction_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.Account.TagTransactionView do + use BlockScoutWeb, :view + + alias Explorer.Account.TagTransaction +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_address_view.ex new file mode 100644 index 000000000000..f3f538399e33 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_address_view.ex @@ -0,0 +1,11 @@ +defmodule BlockScoutWeb.Account.WatchlistAddressView do + use BlockScoutWeb, :view + import BlockScoutWeb.AddressView, only: [trimmed_hash: 1] + import BlockScoutWeb.WeiHelpers, only: [format_wei_value: 2] + + def balance_ether(nil), do: "" + + def balance_ether(balance) do + format_wei_value(balance, :ether) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex new file mode 100644 index 000000000000..fa3966323606 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex @@ -0,0 +1,17 @@ +defmodule BlockScoutWeb.Account.WatchlistView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Account.WatchlistAddressView + alias Explorer.Account.WatchlistAddress + alias Explorer.ExchangeRates.Token + alias Explorer.Market + alias Indexer.Fetcher.CoinBalanceOnDemand + + def coin_balance_status(address) do + CoinBalanceOnDemand.trigger_fetch(address) + end + + def exchange_rate do + Market.get_exchange_rate(Explorer.coin()) || Token.null() + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex index 3a120c597f19..f6e676d7fcdf 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex @@ -3,4 +3,5 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView do alias Explorer.Chain alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.RustVerifierInterface end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex new file mode 100644 index 000000000000..12a80e5eb282 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex @@ -0,0 +1,7 @@ +defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView do + use BlockScoutWeb, :view + + alias Explorer.Chain + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.RustVerifierInterface +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_view.ex index 874b4704067d..385d76c1c12e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_view.ex @@ -1,3 +1,5 @@ defmodule BlockScoutWeb.AddressContractVerificationView do use BlockScoutWeb, :view + + alias Explorer.SmartContract.RustVerifierInterface end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex index 7c7162719597..ce7b4dc00ba1 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex @@ -9,7 +9,7 @@ defmodule BlockScoutWeb.AddressContractView do render_scripts(conn, "address_contract/code_highlighting.js") end - def format_smart_contract_abi(abi), do: Poison.encode!(abi, pretty: false) + def format_smart_contract_abi(abi) when not is_nil(abi), do: Poison.encode!(abi, %{pretty: false}) @doc """ Returns the correct format for the optimization text. @@ -130,7 +130,7 @@ defmodule BlockScoutWeb.AddressContractView do end def creation_code(%Address{contracts_creation_internal_transaction: %InternalTransaction{}} = address) do - address.contracts_creation_internal_transaction.input + address.contracts_creation_internal_transaction.init end def creation_code(%Address{contracts_creation_transaction: %Transaction{}} = address) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex index 47d6525b0489..d070ddf2fd72 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex @@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceView do end def filter_by_type(token_balances, type) do - Enum.filter(token_balances, fn {token_balance, _} -> token_balance.token.type == type end) + Enum.filter(token_balances, fn {_token_balance, token} -> token.type == type end) end @doc """ @@ -30,15 +30,23 @@ defmodule BlockScoutWeb.AddressTokenBalanceView do def sort_by_usd_value_and_name(token_balances) do token_balances |> Enum.sort(fn {token_balance1, token1}, {token_balance2, token2} -> - usd_value1 = token_balance1.token.usd_value - usd_value2 = token_balance2.token.usd_value + usd_value1 = token1.usd_value + usd_value2 = token2.usd_value token_name1 = token1.name token_name2 = token2.name sort_by_name = sort_2_tokens_by_name(token_name1, token_name2) - sort_2_tokens_by_value_desc_and_name(token_balance1, token_balance2, usd_value1, usd_value2, sort_by_name) + sort_2_tokens_by_value_desc_and_name( + token_balance1, + token_balance2, + usd_value1, + usd_value2, + sort_by_name, + token1, + token2 + ) end) end @@ -58,9 +66,17 @@ defmodule BlockScoutWeb.AddressTokenBalanceView do end end - defp sort_2_tokens_by_value_desc_and_name(token_balance1, token_balance2, usd_value1, usd_value2, sort_by_name) + defp sort_2_tokens_by_value_desc_and_name( + token_balance1, + token_balance2, + usd_value1, + usd_value2, + sort_by_name, + token1, + token2 + ) when not is_nil(usd_value1) and not is_nil(usd_value2) do - case Decimal.compare(Chain.balance_in_usd(token_balance1), Chain.balance_in_usd(token_balance2)) do + case Decimal.compare(Chain.balance_in_usd(token_balance1, token1), Chain.balance_in_usd(token_balance2, token2)) do :gt -> true @@ -72,17 +88,41 @@ defmodule BlockScoutWeb.AddressTokenBalanceView do end end - defp sort_2_tokens_by_value_desc_and_name(_token_balance1, _token_balance2, usd_value1, usd_value2, _sort_by_name) + defp sort_2_tokens_by_value_desc_and_name( + _token_balance1, + _token_balance2, + usd_value1, + usd_value2, + _sort_by_name, + _token1, + _token2 + ) when not is_nil(usd_value1) and is_nil(usd_value2) do true end - defp sort_2_tokens_by_value_desc_and_name(_token_balance1, _token_balance2, usd_value1, usd_value2, _sort_by_name) + defp sort_2_tokens_by_value_desc_and_name( + _token_balance1, + _token_balance2, + usd_value1, + usd_value2, + _sort_by_name, + _token1, + _token2 + ) when is_nil(usd_value1) and not is_nil(usd_value2) do false end - defp sort_2_tokens_by_value_desc_and_name(_token_balance1, _token_balance2, usd_value1, usd_value2, sort_by_name) + defp sort_2_tokens_by_value_desc_and_name( + _token_balance1, + _token_balance2, + usd_value1, + usd_value2, + sort_by_name, + _token1, + _token2 + ) when is_nil(usd_value1) and is_nil(usd_value2) do sort_by_name end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index f0b262d2913e..ac75f3e0ddbe 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -4,12 +4,15 @@ defmodule BlockScoutWeb.AddressView do require Logger alias BlockScoutWeb.{AccessHelpers, LayoutView} + alias Explorer.Account.CustomABI alias Explorer.{Chain, CustomContractsHelpers, Repo} alias Explorer.Chain.{Address, Hash, InternalTransaction, SmartContract, Token, TokenTransfer, Transaction, Wei} alias Explorer.Chain.Block.Reward alias Explorer.ExchangeRates.Token, as: TokenExchangeRate alias Explorer.SmartContract.{Helper, Writer} + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + @dialyzer :no_match @tabs [ @@ -178,9 +181,7 @@ defmodule BlockScoutWeb.AddressView do @doc """ Returns the primary name of an address if available. If there is no names on address function performs preload of names association. """ - def primary_name(_, second_time? \\ false) - - def primary_name(%Address{names: [_ | _] = address_names}, _second_time?) do + def primary_name(%Address{names: [_ | _] = address_names}) do case Enum.find(address_names, &(&1.primary == true)) do nil -> %Address.Name{name: name} = Enum.at(address_names, 0) @@ -191,11 +192,20 @@ defmodule BlockScoutWeb.AddressView do end end - def primary_name(%Address{names: _} = address, false) do - primary_name(Repo.preload(address, [:names]), true) + def primary_name(%Address{names: %Ecto.Association.NotLoaded{}} = address) do + primary_name(Repo.preload(address, [:names])) end - def primary_name(%Address{names: _}, true), do: nil + def primary_name(%Address{names: _} = address) do + with false <- is_nil(address.contract_code), + twin <- Chain.get_verified_twin_contract(address), + false <- is_nil(twin) do + twin.name + else + _ -> + nil + end + end def implementation_name(%Address{smart_contract: %{implementation_name: implementation_name}}), do: implementation_name @@ -244,22 +254,22 @@ defmodule BlockScoutWeb.AddressView do def smart_contract_verified?(%Address{smart_contract: nil}), do: false def smart_contract_with_read_only_functions?(%Address{smart_contract: %SmartContract{}} = address) do - Enum.any?(address.smart_contract.abi, &is_read_function?(&1)) + Enum.any?(address.smart_contract.abi || [], &is_read_function?(&1)) end def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false def is_read_function?(function), do: Helper.queriable_method?(function) || Helper.read_with_wallet_method?(function) - def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do - Chain.proxy_contract?(address.hash, address.smart_contract.abi) + def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}) do + SmartContract.proxy_contract?(smart_contract) end def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false def smart_contract_with_write_functions?(%Address{smart_contract: %SmartContract{}} = address) do Enum.any?( - address.smart_contract.abi, + address.smart_contract.abi || [], &Writer.write_function?(&1) ) end @@ -446,4 +456,33 @@ defmodule BlockScoutWeb.AddressView do end def smart_contract_is_gnosis_safe_proxy?(_address), do: false + + def tag_name_to_label(tag_name) do + tag_name + |> String.replace(" ", "-") + end + + def fetch_custom_abi(conn, address_hash) do + if current_user = current_user(conn) do + CustomABI.get_custom_abi_by_identity_id_and_address_hash(address_hash, current_user.id) + end + end + + def has_address_custom_abi_with_read_functions?(conn, address_hash) do + custom_abi = fetch_custom_abi(conn, address_hash) + + check_custom_abi_for_having_read_functions(custom_abi) + end + + def check_custom_abi_for_having_read_functions(custom_abi), + do: !is_nil(custom_abi) && Enum.any?(custom_abi.abi, &is_read_function?(&1)) + + def has_address_custom_abi_with_write_functions?(conn, address_hash) do + custom_abi = fetch_custom_abi(conn, address_hash) + + check_custom_abi_for_having_write_functions(custom_abi) + end + + def check_custom_abi_for_having_write_functions(custom_abi), + do: !is_nil(custom_abi) && Enum.any?(custom_abi.abi, &Writer.write_function?(&1)) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex index f7d2f7b02995..2d9deed6efb4 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex @@ -136,6 +136,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do "index" => to_string(internal_transaction.index), "input" => "#{internal_transaction.input}", "type" => "#{internal_transaction.type}", + "callType" => "#{internal_transaction.call_type}", "gas" => "#{internal_transaction.gas}", "gasUsed" => "#{internal_transaction.gas_used}", "isError" => if(internal_transaction.error, do: "1", else: "0"), @@ -170,13 +171,20 @@ defmodule BlockScoutWeb.API.RPC.AddressView do defp prepare_token_transfer(%{token_type: "ERC-721"} = token_transfer) do token_transfer |> prepare_common_token_transfer() - |> Map.put_new(:tokenID, token_transfer.token_id) + |> Map.put_new(:tokenID, List.first(token_transfer.token_ids)) + end + + defp prepare_token_transfer(%{token_type: "ERC-1155", token_ids: [token_id]} = token_transfer) do + token_transfer + |> prepare_common_token_transfer() + |> Map.put_new(:tokenID, token_id) end defp prepare_token_transfer(%{token_type: "ERC-1155"} = token_transfer) do token_transfer |> prepare_common_token_transfer() - |> Map.put_new(:tokenID, token_transfer.token_id) + |> Map.put_new(:tokenIDs, token_transfer.token_ids) + |> Map.put_new(:values, token_transfer.amounts) end defp prepare_token_transfer(%{token_type: "ERC-20"} = token_transfer) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index e0e9d43ec012..d1686cef1565 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do alias BlockScoutWeb.AddressView alias BlockScoutWeb.API.RPC.RPCView + alias Ecto.Association.NotLoaded alias Explorer.Chain alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract} @@ -34,25 +35,6 @@ defmodule BlockScoutWeb.API.RPC.ContractView do RPCView.render("show.json", data: result) end - defp prepare_source_code_contract(nil) do - %{ - "Address" => "", - "SourceCode" => "", - "ABI" => "Contract source code not verified", - "ContractName" => "", - "CompilerVersion" => "", - "DecompiledSourceCode" => "", - "DecompilerVersion" => decompiler_version(nil), - "OptimizationUsed" => "", - "OptimizationRuns" => "", - "EVMVersion" => "", - "ConstructorArguments" => "", - "ExternalLibraries" => "", - "FileName" => "", - "IsProxy" => "false" - } - end - defp prepare_source_code_contract(address) do decompiled_smart_contract = latest_decompiled_smart_contract(address.decompiled_smart_contracts) contract = address.smart_contract || %{} @@ -70,6 +52,18 @@ defmodule BlockScoutWeb.API.RPC.ContractView do |> set_external_libraries(contract) |> set_verified_contract_data(contract, address, optimization) |> set_proxy_info(contract) + |> set_compiler_settings(contract) + end + + defp set_compiler_settings(contract_output, contract) when contract == %{}, do: contract_output + + defp set_compiler_settings(contract_output, contract) do + if is_nil(contract.compiler_settings) do + contract_output + else + contract_output + |> Map.put(:CompilerSettings, contract.compiler_settings) + end end defp set_proxy_info(contract_output, contract) when contract == %{} do @@ -220,6 +214,8 @@ defmodule BlockScoutWeb.API.RPC.ContractView do } end + defp latest_decompiled_smart_contract(%NotLoaded{}), do: nil + defp latest_decompiled_smart_contract([]), do: nil defp latest_decompiled_smart_contract(contracts) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex new file mode 100644 index 000000000000..6d43e493e195 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -0,0 +1,94 @@ +defmodule BlockScoutWeb.API.V2.AddressView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.AddressView + alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView} + alias BlockScoutWeb.API.V2.Helper + alias Explorer.{Chain, Market} + alias Explorer.Chain.{Address, SmartContract} + alias Explorer.ExchangeRates.Token + + def render("message.json", assigns) do + ApiView.render("message.json", assigns) + end + + def render("address.json", %{address: address, conn: conn}) do + prepare_address(address, conn) + end + + def render("token_balances.json", %{token_balances: token_balances}) do + Enum.map(token_balances, &prepare_token_balance/1) + end + + def render("coin_balance.json", %{coin_balance: coin_balance}) do + prepare_coin_balance_history_entry(coin_balance) + end + + def render("coin_balances.json", %{coin_balances: coin_balances, next_page_params: next_page_params}) do + %{"items" => Enum.map(coin_balances, &prepare_coin_balance_history_entry/1), "next_page_params" => next_page_params} + end + + def render("coin_balances_by_day.json", %{coin_balances_by_day: coin_balances_by_day}) do + Enum.map(coin_balances_by_day, &prepare_coin_balance_history_by_day_entry/1) + end + + def prepare_address(address, conn \\ nil) do + base_info = Helper.address_with_info(conn, address, address.hash) + is_proxy = AddressView.smart_contract_is_proxy?(address) + + {implementation_address, implementation_name} = + with true <- is_proxy, + {address, name} <- SmartContract.get_implementation_address_hash(address.smart_contract), + false <- is_nil(address), + {:ok, address_hash} <- Chain.string_to_address_hash(address), + checksummed_address <- Address.checksum(address_hash) do + {checksummed_address, name} + else + _ -> + {nil, nil} + end + + balance = address.fetched_coin_balance && address.fetched_coin_balance.value + exchange_rate = (Market.get_exchange_rate(Explorer.coin()) || Token.null()).usd_value + + creator_hash = AddressView.from_address_hash(address) + creation_tx = creator_hash && AddressView.transaction_hash(address) + token = address.token && TokenView.render("token.json", %{token: Market.add_price(address.token)}) + + Map.merge(base_info, %{ + "creator_address_hash" => creator_hash && Address.checksum(creator_hash), + "creation_tx_hash" => creation_tx, + "token" => token, + "coin_balance" => balance, + "exchange_rate" => exchange_rate, + "implementation_name" => implementation_name, + "implementation_address" => implementation_address, + "block_number_balance_updated_at" => address.fetched_coin_balance_block_number + }) + end + + def prepare_token_balance({token_balance, token}) do + %{ + "value" => token_balance.value, + "token" => TokenView.render("token.json", %{token: token}), + "token_id" => token_balance.token_id + } + end + + def prepare_coin_balance_history_entry(coin_balance) do + %{ + "transaction_hash" => coin_balance.transaction_hash, + "block_number" => coin_balance.block_number, + "delta" => coin_balance.delta, + "value" => coin_balance.value, + "block_timestamp" => coin_balance.block_timestamp + } + end + + def prepare_coin_balance_history_by_day_entry(coin_balance_by_day) do + %{ + "date" => coin_balance_by_day.date, + "value" => coin_balance_by_day.value + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/api_v2.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/api_v2.ex new file mode 100644 index 000000000000..6bd12a410332 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/api_v2.ex @@ -0,0 +1,9 @@ +defmodule BlockScoutWeb.API.V2 do + @moduledoc """ + API V2 context + """ + + def enabled? do + Application.get_env(:block_scout_web, __MODULE__)[:enabled] + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/api_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/api_view.ex new file mode 100644 index 000000000000..1fde984d4a41 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/api_view.ex @@ -0,0 +1,7 @@ +defmodule BlockScoutWeb.API.V2.ApiView do + def render("message.json", %{message: message}) do + %{ + "message" => message + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex new file mode 100644 index 000000000000..bddcddf08993 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -0,0 +1,106 @@ +defmodule BlockScoutWeb.API.V2.BlockView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.BlockView + alias BlockScoutWeb.API.V2.{ApiView, Helper} + alias Explorer.Chain + alias Explorer.Chain.Block + alias Explorer.Counters.BlockPriorityFeeCounter + + def render("message.json", assigns) do + ApiView.render("message.json", assigns) + end + + def render("blocks.json", %{blocks: blocks, next_page_params: next_page_params}) do + %{"items" => Enum.map(blocks, &prepare_block(&1, nil)), "next_page_params" => next_page_params} + end + + def render("blocks.json", %{blocks: blocks}) do + Enum.map(blocks, &prepare_block(&1, nil)) + end + + def render("block.json", %{block: block, conn: conn}) do + prepare_block(block, conn, true) + end + + def render("block.json", %{block: block, socket: _socket}) do + # single_block? set to true in order to prevent heavy fetching of reward type + prepare_block(block, nil, false) + end + + def prepare_block(block, conn, single_block? \\ false) do + burned_fee = Chain.burned_fees(block.transactions, block.base_fee_per_gas) + priority_fee = block.base_fee_per_gas && BlockPriorityFeeCounter.fetch(block.hash) + + tx_fees = Chain.txn_fees(block.transactions) + + %{ + "height" => block.number, + "timestamp" => block.timestamp, + "tx_count" => count_transactions(block), + "miner" => Helper.address_with_info(conn, block.miner, block.miner_hash), + "size" => block.size, + "hash" => block.hash, + "parent_hash" => block.parent_hash, + "difficulty" => block.difficulty, + "total_difficulty" => block.total_difficulty, + "gas_used" => block.gas_used, + "gas_limit" => block.gas_limit, + "nonce" => block.nonce, + "base_fee_per_gas" => block.base_fee_per_gas, + "burnt_fees" => burned_fee, + "priority_fee" => priority_fee, + "extra_data" => "TODO", + "uncles_hashes" => prepare_uncles(block.uncle_relations), + "state_root" => "TODO", + "rewards" => prepare_rewards(block.rewards, block, single_block?), + "gas_target_percentage" => gas_target(block), + "gas_used_percentage" => gas_used_percentage(block), + "burnt_fees_percentage" => burnt_fees_percentage(burned_fee, tx_fees), + "type" => block |> BlockView.block_type() |> String.downcase(), + "tx_fees" => tx_fees + } + end + + def prepare_rewards(rewards, block, single_block?) do + Enum.map(rewards, &prepare_reward(&1, block, single_block?)) + end + + def prepare_reward(reward, block, single_block?) do + %{ + "reward" => reward.reward, + "type" => if(single_block?, do: BlockView.block_reward_text(reward, block.miner.hash), else: reward.address_type) + } + end + + def prepare_uncles(uncles_relations) when is_list(uncles_relations) do + Enum.map(uncles_relations, &prepare_uncle/1) + end + + def prepare_uncles(_), do: [] + + def prepare_uncle(uncle_relation) do + %{"hash" => uncle_relation.uncle_hash} + end + + def gas_target(block) do + elasticity_multiplier = 2 + ratio = Decimal.div(block.gas_used, Decimal.div(block.gas_limit, elasticity_multiplier)) + ratio |> Decimal.sub(1) |> Decimal.mult(100) |> Decimal.to_float() + end + + def gas_used_percentage(block) do + block.gas_used |> Decimal.div(block.gas_limit) |> Decimal.mult(100) |> Decimal.to_float() + end + + def burnt_fees_percentage(_, %Decimal{coef: 0}), do: nil + + def burnt_fees_percentage(burnt_fees, tx_fees) when not is_nil(tx_fees) and not is_nil(burnt_fees) do + burnt_fees.value |> Decimal.div(tx_fees) |> Decimal.mult(100) |> Decimal.to_float() + end + + def burnt_fees_percentage(_, _), do: nil + + def count_transactions(%Block{transactions: txs}) when is_list(txs), do: Enum.count(txs) + def count_transactions(_), do: nil +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/config_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/config_view.ex new file mode 100644 index 000000000000..be0d9da2c786 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/config_view.ex @@ -0,0 +1,7 @@ +defmodule BlockScoutWeb.API.V2.ConfigView do + def render("json_rpc_url.json", %{url: url}) do + %{ + "json_rpc_url" => url + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex new file mode 100644 index 000000000000..6dc1808e318b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -0,0 +1,122 @@ +defmodule BlockScoutWeb.API.V2.Helper do + @moduledoc """ + API V2 helper + """ + + alias Ecto.Association.NotLoaded + alias Explorer.Chain.Address + alias Explorer.Chain.Transaction.History.TransactionStats + + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2, get_tags_on_address: 1] + + def address_with_info(_, _, nil) do + nil + end + + def address_with_info(conn, address, address_hash) do + %{ + personal_tags: private_tags, + watchlist_names: watchlist_names + } = get_address_tags(address_hash, current_user(conn)) + + public_tags = get_tags_on_address(address_hash) + + Map.merge(address_with_info(address, address_hash), %{ + "private_tags" => private_tags, + "watchlist_names" => watchlist_names, + "public_tags" => public_tags + }) + end + + def address_with_info(%Address{} = address, _address_hash) do + %{ + "hash" => Address.checksum(address), + "is_contract" => is_smart_contract(address), + "name" => address_name(address), + "implementation_name" => implementation_name(address), + "is_verified" => is_verified(address) + } + end + + def address_with_info(%NotLoaded{}, address_hash) do + address_with_info(nil, address_hash) + end + + def address_with_info(nil, nil) do + nil + end + + def address_with_info(nil, address_hash) do + %{ + "hash" => Address.checksum(address_hash), + "is_contract" => false, + "name" => nil, + "implementation_name" => nil, + "is_verified" => nil + } + end + + def address_name(%Address{names: [_ | _] = address_names}) do + case Enum.find(address_names, &(&1.primary == true)) do + nil -> + %Address.Name{name: name} = Enum.at(address_names, 0) + name + + %Address.Name{name: name} -> + name + end + end + + def address_name(_), do: nil + + def implementation_name(%Address{smart_contract: %{implementation_name: implementation_name}}), + do: implementation_name + + def implementation_name(_), do: nil + + def is_smart_contract(%Address{contract_code: nil}), do: false + def is_smart_contract(%Address{contract_code: _}), do: true + def is_smart_contract(%NotLoaded{}), do: nil + def is_smart_contract(_), do: false + + def is_verified(%Address{smart_contract: nil}), do: false + def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil + def is_verified(%Address{smart_contract: _}), do: true + + def market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value}) + when is_nil(available_supply) or is_nil(usd_value) do + Decimal.new(0) + end + + def market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value}) do + Decimal.mult(available_supply, usd_value) + end + + def market_cap(:standard, exchange_rate) do + exchange_rate.market_cap_usd + end + + def market_cap(module, exchange_rate) do + module.market_cap(exchange_rate) + end + + def get_transaction_stats do + stats_scale = date_range(1) + transaction_stats = TransactionStats.by_date_range(stats_scale.earliest, stats_scale.latest) + + # Need datapoint for legend if none currently available. + if Enum.empty?(transaction_stats) do + [%{number_of_transactions: 0, gas_used: 0}] + else + transaction_stats + end + end + + def date_range(num_days) do + today = Date.utc_today() + latest = Date.add(today, -1) + x_days_back = Date.add(latest, -1 * (num_days - 1)) + %{earliest: x_days_back, latest: latest} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex new file mode 100644 index 000000000000..434a6542b4e0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex @@ -0,0 +1,53 @@ +defmodule BlockScoutWeb.API.V2.SearchView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Endpoint + + def render("search_results.json", %{search_results: search_results, next_page_params: next_page_params}) do + %{"items" => Enum.map(search_results, &prepare_search_result/1), "next_page_params" => next_page_params} + end + + def prepare_search_result(%{type: "token"} = search_result) do + %{ + "type" => search_result.type, + "name" => search_result.name, + "symbol" => search_result.symbol, + "address" => search_result.address_hash, + "token_url" => token_path(Endpoint, :show, search_result.address_hash), + "address_url" => address_path(Endpoint, :show, search_result.address_hash) + } + end + + def prepare_search_result(%{type: address_or_contract} = search_result) + when address_or_contract in ["address", "contract"] do + %{ + "type" => search_result.type, + "name" => search_result.name, + "address" => search_result.address_hash, + "url" => address_path(Endpoint, :show, search_result.address_hash) + } + end + + def prepare_search_result(%{type: "block"} = search_result) do + block_hash = hash_to_string(search_result.block_hash) + + %{ + "type" => search_result.type, + "block_number" => search_result.block_number, + "block_hash" => block_hash, + "url" => block_path(Endpoint, :show, block_hash) + } + end + + def prepare_search_result(%{type: "transaction"} = search_result) do + tx_hash = hash_to_string(search_result.tx_hash) + + %{ + "type" => search_result.type, + "tx_hash" => tx_hash, + "url" => transaction_path(Endpoint, :show, tx_hash) + } + end + + defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex new file mode 100644 index 000000000000..66ae64783ffe --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex @@ -0,0 +1,41 @@ +defmodule BlockScoutWeb.API.V2.TokenView do + alias BlockScoutWeb.API.V2.Helper + alias Explorer.Chain.Address + + def render("token.json", %{token: token}) do + %{ + "address" => Address.checksum(token.contract_address_hash), + "symbol" => token.symbol, + "name" => token.name, + "decimals" => token.decimals, + "type" => token.type, + "holders" => token.holder_count && to_string(token.holder_count), + "exchange_rate" => exchange_rate(token), + "total_supply" => token.total_supply + } + end + + def render("token_balances.json", %{ + token_balances: token_balances, + next_page_params: next_page_params, + conn: conn, + token: token + }) do + %{ + "items" => Enum.map(token_balances, &prepare_token_balance(&1, conn, token)), + "next_page_params" => next_page_params + } + end + + def exchange_rate(%{usd_value: usd_value}) when not is_nil(usd_value), do: to_string(usd_value) + def exchange_rate(_), do: nil + + def prepare_token_balance(token_balance, conn, token) do + %{ + "address" => Helper.address_with_info(conn, token_balance.address, token_balance.address_hash), + "value" => token_balance.value, + "token_id" => token_balance.token_id, + "token" => render("token.json", %{token: token}) + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex new file mode 100644 index 000000000000..651547ba7f3c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -0,0 +1,450 @@ +defmodule BlockScoutWeb.API.V2.TransactionView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView} + alias BlockScoutWeb.{ABIEncodedValueView, TransactionView} + alias BlockScoutWeb.Models.GetTransactionTags + alias BlockScoutWeb.Tokens.Helpers + alias Ecto.Association.NotLoaded + alias Explorer.ExchangeRates.Token, as: TokenRate + alias Explorer.{Chain, Market} + alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Token, Transaction, Wei} + alias Explorer.Chain.Block.Reward + alias Explorer.Counters.AverageBlockTime + alias Timex.Duration + + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + + def render("message.json", assigns) do + ApiView.render("message.json", assigns) + end + + def render("transactions.json", %{transactions: transactions, next_page_params: next_page_params, conn: conn}) do + %{"items" => Enum.map(transactions, &prepare_transaction(&1, conn, false)), "next_page_params" => next_page_params} + end + + def render("transactions.json", %{transactions: transactions, conn: conn}) do + Enum.map(transactions, &prepare_transaction(&1, conn, false)) + end + + def render("transaction.json", %{transaction: transaction, conn: conn}) do + prepare_transaction(transaction, conn, true) + end + + def render("raw_trace.json", %{internal_transactions: internal_transactions}) do + InternalTransaction.internal_transactions_to_raw(internal_transactions) + end + + def render("decoded_log_input.json", %{method_id: method_id, text: text, mapping: mapping}) do + %{"method_id" => method_id, "method_call" => text, "parameters" => prepare_log_mapping(mapping)} + end + + def render("decoded_input.json", %{method_id: method_id, text: text, mapping: mapping, error?: _error}) do + %{"method_id" => method_id, "method_call" => text, "parameters" => prepare_method_mapping(mapping)} + end + + def render("revert_reason.json", %{raw: raw}) do + %{"raw" => raw} + end + + def render("token_transfers.json", %{token_transfers: token_transfers, next_page_params: next_page_params, conn: conn}) do + %{"items" => Enum.map(token_transfers, &prepare_token_transfer(&1, conn)), "next_page_params" => next_page_params} + end + + def render("token_transfers.json", %{token_transfers: token_transfers, conn: conn}) do + Enum.map(token_transfers, &prepare_token_transfer(&1, conn)) + end + + def render("token_transfer.json", %{token_transfer: token_transfer, conn: conn}) do + prepare_token_transfer(token_transfer, conn) + end + + def render("internal_transactions.json", %{ + internal_transactions: internal_transactions, + next_page_params: next_page_params, + conn: conn + }) do + %{ + "items" => Enum.map(internal_transactions, &prepare_internal_transaction(&1, conn)), + "next_page_params" => next_page_params + } + end + + def render("logs.json", %{logs: logs, next_page_params: next_page_params, tx_hash: tx_hash}) do + %{"items" => Enum.map(logs, fn log -> prepare_log(log, tx_hash) end), "next_page_params" => next_page_params} + end + + def render("logs.json", %{logs: logs, next_page_params: next_page_params}) do + %{ + "items" => Enum.map(logs, fn log -> prepare_log(log, log.transaction) end), + "next_page_params" => next_page_params + } + end + + def prepare_token_transfer(token_transfer, conn) do + %{ + "tx_hash" => token_transfer.transaction_hash, + "from" => Helper.address_with_info(conn, token_transfer.from_address, token_transfer.from_address_hash), + "to" => Helper.address_with_info(conn, token_transfer.to_address, token_transfer.to_address_hash), + "total" => prepare_token_transfer_total(token_transfer), + "token" => TokenView.render("token.json", %{token: Market.add_price(token_transfer.token)}), + "type" => Chain.get_token_transfer_type(token_transfer), + "timestamp" => block_timestamp(token_transfer.block) + } + end + + def prepare_token_transfer_total(token_transfer) do + case Helpers.token_transfer_amount_for_api(token_transfer) do + {:ok, :erc721_instance} -> + %{"token_id" => List.first(token_transfer.token_ids)} + + {:ok, :erc1155_instance, value, decimals} -> + %{"token_id" => List.first(token_transfer.token_ids), "value" => value, "decimals" => decimals} + + {:ok, :erc1155_instance, values, token_ids, decimals} -> + Enum.map(Enum.zip(values, token_ids), fn {value, token_id} -> + %{"value" => value, "token_id" => token_id, "decimals" => decimals} + end) + + {:ok, value, decimals} -> + %{"value" => value, "decimals" => decimals} + + _ -> + nil + end + end + + def prepare_internal_transaction(internal_transaction, conn) do + %{ + "error" => internal_transaction.error, + "success" => is_nil(internal_transaction.error), + "type" => internal_transaction.call_type, + "transaction_hash" => internal_transaction.transaction_hash, + "from" => + Helper.address_with_info( + conn, + internal_transaction.from_address, + internal_transaction.from_address_hash + ), + "to" => Helper.address_with_info(conn, internal_transaction.to_address, internal_transaction.to_address_hash), + "created_contract" => + Helper.address_with_info( + conn, + internal_transaction.created_contract_address, + internal_transaction.created_contract_address_hash + ), + "value" => internal_transaction.value, + "block" => internal_transaction.block_number, + "timestamp" => internal_transaction.transaction.block.timestamp, + "index" => internal_transaction.index, + "gas_limit" => internal_transaction.gas + } + end + + def prepare_log(log, transaction_or_hash) do + decoded = decode_log(log, transaction_or_hash) + + %{ + "address" => Helper.address_with_info(log.address, log.address_hash), + "topics" => [ + log.first_topic, + log.second_topic, + log.third_topic, + log.fourth_topic + ], + "data" => log.data, + "index" => log.index, + "decoded" => decoded, + "smart_contract" => smart_contract_info(transaction_or_hash) + } + end + + defp smart_contract_info(%Transaction{} = tx), do: Helper.address_with_info(tx.to_address, tx.to_address_hash) + defp smart_contract_info(_), do: nil + + defp decode_log(log, %Transaction{} = tx) do + case log |> Log.decode(tx) |> format_decoded_log_input() do + {:ok, method_id, text, mapping} -> + render(__MODULE__, "decoded_log_input.json", method_id: method_id, text: text, mapping: mapping) + + _ -> + nil + end + end + + defp decode_log(log, transaction_hash), do: decode_log(log, %Transaction{hash: transaction_hash}) + + defp prepare_transaction({%Reward{} = emission_reward, %Reward{} = validator_reward}, conn, _single_tx?) do + %{ + "emission_reward" => emission_reward.reward, + "block_hash" => validator_reward.block_hash, + "from" => Helper.address_with_info(conn, emission_reward.address, emission_reward.address_hash), + "to" => Helper.address_with_info(conn, validator_reward.address, validator_reward.address_hash), + "types" => [:reward] + } + end + + defp prepare_transaction(%Transaction{} = transaction, conn, single_tx?) do + base_fee_per_gas = transaction.block && transaction.block.base_fee_per_gas + max_priority_fee_per_gas = transaction.max_priority_fee_per_gas + max_fee_per_gas = transaction.max_fee_per_gas + + priority_fee_per_gas = priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) + + burned_fee = burned_fee(transaction, max_fee_per_gas, base_fee_per_gas) + + status = transaction |> Chain.transaction_to_status() |> format_status() + + revert_reason = revert_reason(status, transaction) + + decoded_input = transaction |> Transaction.decoded_input_data() |> format_decoded_input() + decoded_input_data = decoded_input(decoded_input) + + %{ + "hash" => transaction.hash, + "result" => status, + "status" => transaction.status, + "block" => transaction.block_number, + "timestamp" => block_timestamp(transaction.block), + "from" => Helper.address_with_info(conn, transaction.from_address, transaction.from_address_hash), + "to" => Helper.address_with_info(conn, transaction.to_address, transaction.to_address_hash), + "created_contract" => + Helper.address_with_info(conn, transaction.created_contract_address, transaction.created_contract_address_hash), + "confirmations" => + transaction.block |> Chain.confirmations(block_height: Chain.block_height()) |> format_confirmations(), + "confirmation_duration" => processing_time_duration(transaction), + "value" => transaction.value, + "fee" => transaction |> Chain.fee(:wei) |> format_fee(), + "gas_price" => transaction.gas_price, + "type" => transaction.type, + "gas_used" => transaction.gas_used, + "gas_limit" => transaction.gas, + "max_fee_per_gas" => transaction.max_fee_per_gas, + "max_priority_fee_per_gas" => transaction.max_priority_fee_per_gas, + "base_fee_per_gas" => base_fee_per_gas, + "priority_fee" => priority_fee_per_gas && Wei.mult(priority_fee_per_gas, transaction.gas_used), + "tx_burnt_fee" => burned_fee, + "nonce" => transaction.nonce, + "position" => transaction.index, + "revert_reason" => revert_reason, + "raw_input" => transaction.input, + "decoded_input" => decoded_input_data, + "token_transfers" => token_transfers(transaction.token_transfers, conn, single_tx?), + "token_transfers_overflow" => token_transfers_overflow(transaction.token_transfers, single_tx?), + "exchange_rate" => (Market.get_exchange_rate(Explorer.coin()) || TokenRate.null()).usd_value, + "method" => method_name(transaction, decoded_input), + "tx_types" => tx_types(transaction), + "tx_tag" => GetTransactionTags.get_transaction_tags(transaction.hash, current_user(conn)) + } + end + + def token_transfers(_, _conn, false), do: nil + def token_transfers(%NotLoaded{}, _conn, _), do: nil + + def token_transfers(token_transfers, conn, _) do + render("token_transfers.json", %{ + token_transfers: Enum.take(token_transfers, Chain.get_token_transfers_per_transaction_preview_count()), + conn: conn + }) + end + + def token_transfers_overflow(_, false), do: nil + def token_transfers_overflow(%NotLoaded{}, _), do: false + + def token_transfers_overflow(token_transfers, _), + do: Enum.count(token_transfers) > Chain.get_token_transfers_per_transaction_preview_count() + + defp priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) do + if is_nil(max_priority_fee_per_gas) or is_nil(base_fee_per_gas), + do: nil, + else: + Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x -> + Wei.to(x, :wei) + end) + end + + defp burned_fee(transaction, max_fee_per_gas, base_fee_per_gas) do + if !is_nil(max_fee_per_gas) and !is_nil(transaction.gas_used) and !is_nil(base_fee_per_gas) do + if Decimal.compare(max_fee_per_gas.value, 0) == :eq do + %Wei{value: Decimal.new(0)} + else + Wei.mult(base_fee_per_gas, transaction.gas_used) + end + else + nil + end + end + + defp revert_reason(status, transaction) do + if is_binary(status) && status |> String.downcase() |> String.contains?("reverted") do + case TransactionView.transaction_revert_reason(transaction) do + {:error, _contract_not_verified, candidates} when candidates != [] -> + {:ok, method_id, text, mapping} = Enum.at(candidates, 0) + render(__MODULE__, "decoded_input.json", method_id: method_id, text: text, mapping: mapping, error?: true) + + {:ok, method_id, text, mapping} -> + render(__MODULE__, "decoded_input.json", method_id: method_id, text: text, mapping: mapping, error?: true) + + _ -> + hex = TransactionView.get_pure_transaction_revert_reason(transaction) + render(__MODULE__, "revert_reason.json", raw: hex) + end + end + end + + defp decoded_input(decoded_input) do + case decoded_input do + {:ok, method_id, text, mapping} -> + render(__MODULE__, "decoded_input.json", method_id: method_id, text: text, mapping: mapping, error?: false) + + _ -> + nil + end + end + + def prepare_method_mapping(mapping) do + Enum.map(mapping, fn {name, type, value} -> + %{"name" => name, "type" => type, "value" => ABIEncodedValueView.value_json(type, value)} + end) + end + + def prepare_log_mapping(mapping) do + Enum.map(mapping, fn {name, type, indexed?, value} -> + %{"name" => name, "type" => type, "indexed" => indexed?, "value" => ABIEncodedValueView.value_json(type, value)} + end) + end + + defp format_status({:error, reason}), do: reason + defp format_status(status), do: status + + defp format_decoded_input({:error, _, []}), do: nil + defp format_decoded_input({:error, _, candidates}), do: Enum.at(candidates, 0) + defp format_decoded_input({:ok, _identifier, _text, _mapping} = decoded), do: decoded + defp format_decoded_input(_), do: nil + + defp format_decoded_log_input({:error, :could_not_decode}), do: nil + defp format_decoded_log_input({:error, :no_matching_function}), do: nil + defp format_decoded_log_input({:ok, _method_id, _text, _mapping} = decoded), do: decoded + defp format_decoded_log_input({:error, _, candidates}), do: Enum.at(candidates, 0) + + def format_confirmations({:ok, confirmations}), do: confirmations + def format_confirmations(_), do: 0 + + def format_fee({type, value}), do: %{"type" => type, "value" => value} + + def processing_time_duration(%Transaction{block: nil}) do + [] + end + + def processing_time_duration(%Transaction{earliest_processing_start: nil}) do + avg_time = AverageBlockTime.average_block_time() + + if avg_time == {:error, :disabled} do + [] + else + [ + 0, + avg_time + |> Duration.to_milliseconds() + ] + end + end + + def processing_time_duration(%Transaction{ + block: %Block{timestamp: end_time}, + earliest_processing_start: earliest_processing_start, + inserted_at: inserted_at + }) do + long_interval = abs(diff(earliest_processing_start, end_time)) + short_interval = abs(diff(inserted_at, end_time)) + merge_intervals(short_interval, long_interval) + end + + def merge_intervals(short, long) when short == long, do: [short] + + def merge_intervals(short, long) do + [short, long] + end + + def diff(left, right) do + left + |> Timex.diff(right, :milliseconds) + end + + defp method_name(_, {:ok, _method_id, text, _mapping}) do + Transaction.parse_method_name(text, false) + end + + defp method_name(%Transaction{to_address: to_address, input: %{bytes: <>}}, _) do + if Helper.is_smart_contract(to_address) do + "0x" <> Base.encode16(method_id, case: :lower) + else + nil + end + end + + defp method_name(_, _) do + nil + end + + defp tx_types(tx, types \\ [], stage \\ :token_transfer) + + defp tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do + types = + if !is_nil(token_transfers) && token_transfers != [] && !match?(%NotLoaded{}, token_transfers) do + [:token_transfer | types] + else + types + end + + tx_types(tx, types, :token_creation) + end + + defp tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do + types = + if match?(%Address{}, created_contract_address) && match?(%Token{}, created_contract_address.token) do + [:token_creation | types] + else + types + end + + tx_types(tx, types, :contract_creation) + end + + defp tx_types( + %Transaction{created_contract_address_hash: created_contract_address_hash} = tx, + types, + :contract_creation + ) do + types = + if is_nil(created_contract_address_hash) do + types + else + [:contract_creation | types] + end + + tx_types(tx, types, :contract_call) + end + + defp tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do + types = + if Helper.is_smart_contract(to_address) do + [:contract_call | types] + else + types + end + + tx_types(tx, types, :coin_transfer) + end + + defp tx_types(%Transaction{value: value}, types, :coin_transfer) do + if Decimal.compare(value.value, 0) == :gt do + [:coin_transfer | types] + else + types + end + end + + defp block_timestamp(%Block{} = block), do: block.timestamp + defp block_timestamp(_), do: nil +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex index 883440b48555..20260243b910 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.APIDocsView do use BlockScoutWeb, :view alias BlockScoutWeb.LayoutView + alias Explorer def action_tile_id(module, action) do "#{module}-#{action}" @@ -20,7 +21,7 @@ defmodule BlockScoutWeb.APIDocsView do end def model_type_definition(definition_func) when is_function(definition_func, 1) do - coin = Application.get_env(:explorer, :coin) + coin = Explorer.coin() definition_func.(coin) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex index 78a22e76700b..7fdabe4b5ef9 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.BlockView do import Math.Enum, only: [mean: 1] + alias Ecto.Association.NotLoaded alias Explorer.Chain alias Explorer.Chain.{Block, Wei} alias Explorer.Chain.Block.Reward @@ -23,6 +24,7 @@ defmodule BlockScoutWeb.BlockView do "#{average} #{unit_text}" end + def block_type(%Block{consensus: false, nephews: %NotLoaded{}}), do: "Reorg" def block_type(%Block{consensus: false, nephews: []}), do: "Reorg" def block_type(%Block{consensus: false}), do: "Uncle" def block_type(_block), do: "Block" diff --git a/apps/block_scout_web/lib/block_scout_web/views/captcha_view.ex b/apps/block_scout_web/lib/block_scout_web/views/captcha_view.ex deleted file mode 100644 index 8da20e685f13..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/views/captcha_view.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule BlockScoutWeb.CaptchaView do - use BlockScoutWeb, :view -end diff --git a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex index ff5a737bfb68..43d66a29266d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex @@ -3,27 +3,11 @@ defmodule BlockScoutWeb.ChainView do require Decimal import Number.Currency, only: [number_to_currency: 2] + import BlockScoutWeb.API.V2.Helper, only: [market_cap: 2] alias BlockScoutWeb.LayoutView alias Explorer.Chain.Cache.GasPriceOracle - defp market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value}) - when is_nil(available_supply) or is_nil(usd_value) do - Decimal.new(0) - end - - defp market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value}) do - Decimal.mult(available_supply, usd_value) - end - - defp market_cap(:standard, exchange_rate) do - exchange_rate.market_cap_usd - end - - defp market_cap(module, exchange_rate) do - module.market_cap(exchange_rate) - end - def format_usd_value(nil), do: "" def format_usd_value(value) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/error_view.ex b/apps/block_scout_web/lib/block_scout_web/views/error_view.ex index 22da72d0a26d..d800b719b3b2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/error_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/error_view.ex @@ -1,15 +1,35 @@ defmodule BlockScoutWeb.ErrorView do use BlockScoutWeb, :view - def render("404.html", _assigns) do + # when type in ["json", "html"] + def render("404." <> _type, _assigns) do "Page not found" end - def render("422.html", _assigns) do + def render("400." <> _type, _assigns) do + "Bad request" + end + + def render("401." <> _type, _assigns) do + "Unauthorized" + end + + def render("403." <> _type, _assigns) do + "Forbidden" + end + + def render("422." <> _type, _assigns) do "Unprocessable entity" end - def render("500.html", _assigns) do + def render("500.html", %{conn: conn}) do + render(BlockScoutWeb.InternalServerErrorView, "index.html", + layout: {BlockScoutWeb.LayoutView, "app.html"}, + conn: conn + ) + end + + def render("500." <> _type, _assigns) do "Internal server error" end diff --git a/apps/block_scout_web/lib/block_scout_web/views/internal_server_error_view.ex b/apps/block_scout_web/lib/block_scout_web/views/internal_server_error_view.ex new file mode 100644 index 000000000000..837ecb3367e0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/internal_server_error_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.InternalServerErrorView do + use BlockScoutWeb, :view + + @dialyzer :no_match +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex index b1af922bff07..2e77e19c6ddd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex @@ -7,7 +7,6 @@ defmodule BlockScoutWeb.LayoutView do import BlockScoutWeb.AddressView, only: [from_address_hash: 1] - @issue_url "https://github.com/blockscout/blockscout/issues/new" @default_other_networks [ %{ title: "POA", @@ -71,7 +70,9 @@ defmodule BlockScoutWeb.LayoutView do title: subnetwork_title() <> ": " ] - [@issue_url, "?", URI.encode_query(params)] + issue_url = "#{Application.get_env(:block_scout_web, :footer)[:github_link]}/issues/new" + + [issue_url, "?", URI.encode_query(params)] end defp issue_body(conn) do @@ -228,11 +229,12 @@ defmodule BlockScoutWeb.LayoutView do end end - def external_apps_list do - if Application.get_env(:block_scout_web, :external_apps) do + def apps_list do + apps = Application.get_env(:block_scout_web, :apps) + + if apps do try do - :block_scout_web - |> Application.get_env(:external_apps) + apps |> Parser.parse!(%{keys: :atoms!}) rescue _ -> @@ -251,4 +253,29 @@ defmodule BlockScoutWeb.LayoutView do end defp validate_url(_), do: :error + + def sign_in_link do + if Mix.env() == :test do + "/auth/auth0" + else + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] <> "/auth/auth0" + end + end + + def sign_out_link do + client_id = Application.get_env(:ueberauth, Ueberauth.Strategy.Auth0.OAuth)[:client_id] + return_to = Application.get_env(:ueberauth, Ueberauth)[:logout_return_to_url] + logout_url = Application.get_env(:ueberauth, Ueberauth)[:logout_url] + + if client_id && return_to && logout_url do + params = [ + client_id: client_id, + returnTo: return_to + ] + + [logout_url, "?", URI.encode_query(params)] + else + [] + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex index 7b8d3276d63a..6cd70d326733 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex @@ -16,31 +16,31 @@ defmodule BlockScoutWeb.Tokens.Helpers do When the token's type is ERC-721, the function will return a string with the token_id that represents the ERC-721 token since this kind of token doesn't have amount and decimals. """ - def token_transfer_amount(%{token: token, amount: amount, amounts: amounts, token_id: token_id, token_ids: token_ids}) do - do_token_transfer_amount(token, amount, amounts, token_id, token_ids) + def token_transfer_amount(%{token: token, amount: amount, amounts: amounts, token_ids: token_ids}) do + do_token_transfer_amount(token, amount, amounts, token_ids) end - def token_transfer_amount(%{token: token, amount: amount, token_id: token_id}) do - do_token_transfer_amount(token, amount, nil, token_id, nil) + def token_transfer_amount(%{token: token, amount: amount, token_ids: token_ids}) do + do_token_transfer_amount(token, amount, nil, token_ids) end - defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, nil, _token_id, _token_ids) do + defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, nil, _token_ids) do {:ok, "--"} end - defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, amount, _amounts, _token_id, _token_ids) do + defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, amount, _amounts, _token_ids) do {:ok, CurrencyHelpers.format_according_to_decimals(amount, Decimal.new(0))} end - defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: decimals}, amount, _amounts, _token_id, _token_ids) do + defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: decimals}, amount, _amounts, _token_ids) do {:ok, CurrencyHelpers.format_according_to_decimals(amount, decimals)} end - defp do_token_transfer_amount(%Token{type: "ERC-721"}, _amount, _amounts, _token_id, _token_ids) do + defp do_token_transfer_amount(%Token{type: "ERC-721"}, _amount, _amounts, _token_ids) do {:ok, :erc721_instance} end - defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, amounts, _token_id, token_ids) do + defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, amounts, token_ids) do if amount do {:ok, :erc1155_instance, CurrencyHelpers.format_according_to_decimals(amount, decimals)} else @@ -48,7 +48,54 @@ defmodule BlockScoutWeb.Tokens.Helpers do end end - defp do_token_transfer_amount(_token, _amount, _amounts, _token_id, _token_ids) do + defp do_token_transfer_amount(_token, _amount, _amounts, _token_ids) do + nil + end + + def token_transfer_amount_for_api(%{ + token: token, + amount: amount, + amounts: amounts, + token_ids: token_ids + }) do + do_token_transfer_amount_for_api(token, amount, amounts, token_ids) + end + + def token_transfer_amount_for_api(%{token: token, amount: amount, token_ids: token_ids}) do + do_token_transfer_amount_for_api(token, amount, nil, token_ids) + end + + defp do_token_transfer_amount_for_api(%Token{type: "ERC-20"}, nil, nil, _token_ids) do + {:ok, nil} + end + + defp do_token_transfer_amount_for_api( + %Token{type: "ERC-20", decimals: decimals}, + amount, + _amounts, + _token_ids + ) do + {:ok, amount, decimals} + end + + defp do_token_transfer_amount_for_api(%Token{type: "ERC-721"}, _amount, _amounts, _token_ids) do + {:ok, :erc721_instance} + end + + defp do_token_transfer_amount_for_api( + %Token{type: "ERC-1155", decimals: decimals}, + amount, + amounts, + token_ids + ) do + if amount do + {:ok, :erc1155_instance, amount, decimals} + else + {:ok, :erc1155_instance, amounts, token_ids, decimals} + end + end + + defp do_token_transfer_amount_for_api(_token, _amount, _amounts, _token_ids) do nil end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/metadata_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/metadata_view.ex index 5a19f3968436..758e6a3bd276 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/metadata_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/metadata_view.ex @@ -5,5 +5,5 @@ defmodule BlockScoutWeb.Tokens.Instance.MetadataView do def format_metadata(nil), do: "" - def format_metadata(metadata), do: Poison.encode!(metadata, pretty: true) + def format_metadata(metadata), do: Poison.encode!(metadata, %{pretty: true}) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex index 2da0d1e63e42..1c0c748dc415 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex @@ -53,25 +53,27 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do end end + def media_type("data:image/" <> _data) do + "image" + end + + def media_type("data:video/" <> _data) do + "video" + end + + def media_type("data:" <> _data) do + nil + end + def media_type(media_src) when not is_nil(media_src) do ext = media_src |> Path.extname() |> String.trim() mime_type = if ext == "" do - case HTTPoison.get(media_src) do - {:ok, %HTTPoison.Response{body: body, status_code: 200}} -> - {:ok, path} = Briefly.create() - - File.write!(path, body) - - case FileInfo.get_info([path]) do - %{^path => %FileInfo.Mime{subtype: subtype}} -> - subtype - |> MIME.type() - - _ -> - nil - end + case HTTPoison.head(media_src, [], follow_redirect: true) do + {:ok, %HTTPoison.Response{status_code: 200, headers: headers}} -> + headers_map = Map.new(headers, fn {key, value} -> {String.downcase(key), value} end) + headers_map["content-type"] _ -> nil @@ -120,7 +122,7 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do def smart_contract_with_read_only_functions?( %Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token ) do - Enum.any?(token.contract_address.smart_contract.abi, &Helper.queriable_method?(&1)) + Enum.any?(token.contract_address.smart_contract.abi || [], &Helper.queriable_method?(&1)) end def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index e258938f1b95..4fc1c6d28ef4 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -48,13 +48,13 @@ defmodule BlockScoutWeb.Tokens.OverviewView do def smart_contract_with_read_only_functions?( %Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token ) do - Enum.any?(token.contract_address.smart_contract.abi, &Helper.queriable_method?(&1)) + Enum.any?(token.contract_address.smart_contract.abi || [], &Helper.queriable_method?(&1)) end def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false - def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{}} = address}) do - Chain.proxy_contract?(address.hash, address.smart_contract.abi) + def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}}) do + SmartContract.proxy_contract?(smart_contract) end def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false @@ -63,7 +63,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do contract_address: %Address{smart_contract: %SmartContract{}} = address }) do Enum.any?( - address.smart_contract.abi, + address.smart_contract.abi || [], &Writer.write_function?(&1) ) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex new file mode 100644 index 000000000000..27b5950c4ce8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex @@ -0,0 +1,48 @@ +defmodule BlockScoutWeb.TransactionStateView do + use BlockScoutWeb, :view + + alias Explorer.Chain + alias Explorer.Chain.Wei + + import BlockScoutWeb.TransactionStateController, only: [from_loss: 1, to_profit: 1] + + def has_diff?(%Wei{value: val}) do + not Decimal.eq?(val, Decimal.new(0)) + end + + def has_diff?(val) do + not Decimal.eq?(val, Decimal.new(0)) + end + + def not_negative?(%Wei{value: val}) do + not Decimal.negative?(val) + end + + def not_negative?(val) do + not Decimal.negative?(val) + end + + def absolute_value_of(%Wei{value: val}) do + %Wei{value: Decimal.abs(val)} + end + + def absolute_value_of(val) do + Decimal.abs(val) + end + + def has_state_changes?(tx) do + has_diff?(from_loss(tx)) or has_diff?(to_profit(tx)) + end + + def display_value(balance, :coin) do + format_wei_value(balance, :ether) + end + + def display_value(balance, token_transfer) do + render("_token_balance.html", transfer: token_transfer, balance: balance) + end + + def display_nft(token_transfer) do + render(BlockScoutWeb.TransactionView, "_total_transfers.html", transfer: token_transfer) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 31d9feffff5a..df5e4bd72396 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.TransactionView do use BlockScoutWeb, :view alias BlockScoutWeb.{AccessHelpers, AddressView, BlockView, TabHelpers} + alias BlockScoutWeb.Account.AuthController alias BlockScoutWeb.Cldr.Number alias Explorer.{Chain, CustomContractsHelpers, Repo} alias Explorer.Chain.Block.Reward @@ -11,10 +12,10 @@ defmodule BlockScoutWeb.TransactionView do alias Timex.Duration import BlockScoutWeb.Gettext - import BlockScoutWeb.AddressView, only: [from_address_hash: 1, short_token_id: 2] + import BlockScoutWeb.AddressView, only: [from_address_hash: 1, short_token_id: 2, tag_name_to_label: 1] import BlockScoutWeb.Tokens.Helpers - @tabs ["token-transfers", "internal-transactions", "logs", "raw-trace"] + @tabs ["token-transfers", "internal-transactions", "logs", "raw-trace", "state"] @token_burning_title "Token Burning" @token_minting_title "Token Minting" @@ -140,8 +141,7 @@ defmodule BlockScoutWeb.TransactionView do token: token_transfer.token, amount: nil, amounts: [], - token_id: token_transfer.token_id, - token_ids: [], + token_ids: token_transfer.token_ids, to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash } @@ -155,7 +155,6 @@ defmodule BlockScoutWeb.TransactionView do token: token_transfer.token, amount: nil, amounts: amounts, - token_id: nil, token_ids: token_transfer.token_ids, to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash @@ -169,8 +168,7 @@ defmodule BlockScoutWeb.TransactionView do token: token_transfer.token, amount: token_transfer.amount, amounts: [], - token_id: token_transfer.token_id, - token_ids: [], + token_ids: token_transfer.token_ids, to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash } @@ -512,6 +510,7 @@ defmodule BlockScoutWeb.TransactionView do defp tab_name(["internal-transactions"]), do: gettext("Internal Transactions") defp tab_name(["logs"]), do: gettext("Logs") defp tab_name(["raw-trace"]), do: gettext("Raw Trace") + defp tab_name(["state"]), do: gettext("State changes") defp get_transaction_type_from_token_transfers(token_transfers) do token_transfers_types = diff --git a/apps/block_scout_web/lib/block_scout_web/views/verified_contracts_view.ex b/apps/block_scout_web/lib/block_scout_web/views/verified_contracts_view.ex new file mode 100644 index 000000000000..e54179147ee6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/verified_contracts_view.ex @@ -0,0 +1,14 @@ +defmodule BlockScoutWeb.VerifiedContractsView do + use BlockScoutWeb, :view + + import BlockScoutWeb.AddressView, only: [balance: 1] + alias BlockScoutWeb.WebRouter.Helpers + + def format_current_filter(filter) do + case filter do + "solidity" -> gettext("Solidity") + "vyper" -> gettext("Vyper") + _ -> gettext("All") + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/visualize_sol2uml_view.ex b/apps/block_scout_web/lib/block_scout_web/views/visualize_sol2uml_view.ex new file mode 100644 index 000000000000..827deeeecdec --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/visualize_sol2uml_view.ex @@ -0,0 +1,3 @@ +defmodule BlockScoutWeb.VisualizeSol2umlView do + use BlockScoutWeb, :view +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex index 1a9ed2c50f6d..1239f32f7155 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex @@ -35,7 +35,7 @@ defmodule BlockScoutWeb.WeiHelpers do "10,000 Gwei" iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 21)}, :ether) - "10,000 Ether" + "10,000 ETH" # With formatting options @@ -43,7 +43,7 @@ defmodule BlockScoutWeb.WeiHelpers do ...> %Wei{value: Decimal.new(1000500000000000000)}, ...> :ether ...> ) - "1.0005 Ether" + "1.0005 ETH" iex> format_wei_value( ...> %Wei{value: Decimal.new(10)}, @@ -75,5 +75,5 @@ defmodule BlockScoutWeb.WeiHelpers do defp display_unit(:wei), do: gettext("Wei") defp display_unit(:gwei), do: gettext("Gwei") - defp display_unit(:ether), do: gettext("Ether") + defp display_unit(:ether), do: Explorer.coin_name() end diff --git a/apps/block_scout_web/lib/block_scout_web/web_router.ex b/apps/block_scout_web/lib/block_scout_web/web_router.ex index 58d16d27d1b7..d039ae9b1236 100644 --- a/apps/block_scout_web/lib/block_scout_web/web_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex @@ -3,6 +3,9 @@ defmodule BlockScoutWeb.WebRouter do Router for web app """ use BlockScoutWeb, :router + require Ueberauth + + alias BlockScoutWeb.Plug.CheckAccountWeb pipeline :browser do plug(:accepts, ["html"]) @@ -13,6 +16,69 @@ defmodule BlockScoutWeb.WebRouter do plug(BlockScoutWeb.ChecksumAddress) end + pipeline :account do + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(CheckAccountWeb) + plug(:protect_from_forgery) + plug(BlockScoutWeb.CSPHeader) + plug(BlockScoutWeb.ChecksumAddress) + end + + if Mix.env() == :dev do + forward("/sent_emails", Bamboo.SentEmailViewerPlug) + end + + scope "/auth", BlockScoutWeb do + pipe_through(:account) + + get("/profile", Account.AuthController, :profile) + get("/logout", Account.AuthController, :logout) + get("/:provider", Account.AuthController, :request) + get("/:provider/callback", Account.AuthController, :callback) + end + + scope "/account", BlockScoutWeb do + pipe_through(:account) + + resources("/tag_address", Account.TagAddressController, + only: [:index, :new, :create, :delete], + as: :tag_address + ) + + resources("/tag_transaction", Account.TagTransactionController, + only: [:index, :new, :create, :delete], + as: :tag_transaction + ) + + resources("/watchlist", Account.WatchlistController, + only: [:show], + singleton: true, + as: :watchlist + ) + + resources("/watchlist_address", Account.WatchlistAddressController, + only: [:new, :create, :edit, :update, :delete], + as: :watchlist_address + ) + + resources("/api_key", Account.ApiKeyController, + only: [:new, :create, :edit, :update, :delete, :index], + as: :api_key + ) + + resources("/custom_abi", Account.CustomABIController, + only: [:new, :create, :edit, :update, :delete, :index], + as: :custom_abi + ) + + resources("/public_tags_request", Account.PublicTagsRequestController, + only: [:new, :create, :edit, :update, :delete, :index], + as: :public_tags_request + ) + end + # Disallows Iframes (write routes) scope "/", BlockScoutWeb do pipe_through(:browser) @@ -40,7 +106,10 @@ defmodule BlockScoutWeb.WebRouter do resources("/blocks", BlockController, as: :blocks, only: [:index]) - resources "/blocks", BlockController, as: :block_secondary, only: [:show], param: "hash_or_number" do + resources "/blocks", BlockController, + as: :block_secondary, + only: [:show], + param: "hash_or_number" do resources("/transactions", BlockTransactionController, only: [:index], as: :transaction) end @@ -52,6 +121,8 @@ defmodule BlockScoutWeb.WebRouter do resources("/recent-transactions", RecentTransactionsController, only: [:index]) + resources("/verified-contracts", VerifiedContractsController, only: [:index]) + get("/txs", TransactionController, :index) resources "/tx", TransactionController, only: [:show] do @@ -75,6 +146,11 @@ defmodule BlockScoutWeb.WebRouter do only: [:index], as: :token_transfer ) + + resources("/state", TransactionStateController, + only: [:index], + as: :state + ) end resources("/accounts", AddressController, only: [:index]) @@ -147,6 +223,13 @@ defmodule BlockScoutWeb.WebRouter do as: :verify_contract_via_standard_json_input ) + resources( + "/verify-via-multi-part-files", + AddressContractVerificationViaMultiPartFilesController, + only: [:new], + as: :verify_contract_via_multi_part_files + ) + resources( "/verify-vyper-contract", AddressContractVerificationVyperController, @@ -393,9 +476,9 @@ defmodule BlockScoutWeb.WebRouter do get("/search-results", SearchController, :search_results) - get("/csv-export", CsvExportController, :index) + get("/search-verified-contracts", VerifiedContractsController, :search_verified_contracts) - post("/captcha", CaptchaController, :index) + get("/csv-export", CsvExportController, :index) get("/transactions-csv", AddressTransactionController, :transactions_csv) @@ -411,6 +494,8 @@ defmodule BlockScoutWeb.WebRouter do get("/token-counters", Tokens.TokenController, :token_counters) + get("/visualize/sol2uml", VisualizeSol2umlController, :index) + get("/*path", PageNotFoundController, :index) end end diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index e76836d8ecbd..a166d2a67447 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -7,7 +7,6 @@ defmodule BlockScoutWeb.Mixfile do app: :block_scout_web, build_path: "../../_build", config_path: "../../config/config.exs", - compilers: [:phoenix, :gettext | Mix.compilers()], deps: deps(), deps_path: "../../deps", description: "Web interface for BlockScout.", @@ -15,7 +14,7 @@ defmodule BlockScoutWeb.Mixfile do plt_add_deps: :transitive, ignore_warnings: "../../.dialyzer-ignore" ], - elixir: "~> 1.10", + elixir: "~> 1.13", elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", package: package(), @@ -24,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "0.0.1" + version: "4.1.8" ] end @@ -45,6 +44,7 @@ defmodule BlockScoutWeb.Mixfile do defp extra_applications, do: [ + :ueberauth_auth0, :logger, :runtime_tools ] @@ -62,9 +62,9 @@ defmodule BlockScoutWeb.Mixfile do {:absinthe_plug, git: "https://github.com/blockscout/absinthe_plug.git", tag: "1.5.3", override: true}, # Absinthe support for the Relay framework {:absinthe_relay, "~> 1.5"}, - {:bypass, "~> 1.0", only: :test}, + {:bypass, "~> 2.1", only: :test}, # To add (CORS)(https://www.w3.org/TR/cors/) - {:cors_plug, "~> 2.0"}, + {:cors_plug, "~> 3.0"}, {:credo, "~> 1.5", only: :test, runtime: false}, # For Absinthe to load data in batches {:dataloader, "~> 1.0.0"}, @@ -74,7 +74,7 @@ defmodule BlockScoutWeb.Mixfile do {:ecto, "~> 3.3", override: true}, {:ex_cldr, "~> 2.7"}, {:ex_cldr_numbers, "~> 2.6"}, - {:ex_cldr_units, "~> 2.5"}, + {:ex_cldr_units, "~> 3.13"}, {:cldr_utils, "~> 2.3"}, {:ex_machina, "~> 2.1", only: [:test]}, {:explorer, in_umbrella: true}, @@ -82,8 +82,8 @@ defmodule BlockScoutWeb.Mixfile do {:file_info, "~> 0.0.4"}, # HTML CSS selectors for Phoenix controller tests {:floki, "~> 0.31"}, - {:flow, "~> 0.12"}, - {:gettext, "~> 0.18.2"}, + {:flow, "~> 1.2"}, + {:gettext, "~> 0.20.0"}, {:hammer, "~> 6.0"}, {:httpoison, "~> 1.6"}, {:indexer, in_umbrella: true, runtime: false}, @@ -92,7 +92,7 @@ defmodule BlockScoutWeb.Mixfile do {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, # Log errors and application output to separate files {:logger_file_backend, "~> 0.0.10"}, - {:math, "~> 0.3.0"}, + {:math, "~> 0.7.0"}, {:mock, "~> 0.3.0", only: [:test], runtime: false}, {:number, "~> 1.0.1"}, {:phoenix, "== 1.5.13"}, @@ -100,6 +100,7 @@ defmodule BlockScoutWeb.Mixfile do {:phoenix_html, "== 3.0.4"}, {:phoenix_live_reload, "~> 1.2", only: [:dev]}, {:phoenix_pubsub, "~> 2.0"}, + {:prometheus_ex, git: "https://github.com/lanodan/prometheus.ex", branch: "fix/elixir-1.14", override: true}, # use `:cowboy` for WebServer with `:plug` {:plug_cowboy, "~> 2.2"}, # Waiting for the Pretty Print to be implemented at the Jason lib @@ -124,11 +125,14 @@ defmodule BlockScoutWeb.Mixfile do # `:spandex` tracing of `:phoenix` {:spandex_phoenix, "~> 1.0"}, {:timex, "~> 3.7.1"}, - {:wallaby, "~> 0.28", only: :test, runtime: false}, + {:wallaby, "~> 0.30", only: :test, runtime: false}, # `:cowboy` `~> 2.0` and Phoenix 1.4 compatibility {:websocket_client, "~> 1.3"}, {:wobserver, "~> 0.2.0", github: "poanetwork/wobserver", branch: "support-https"}, - {:ex_json_schema, "~> 0.6.2"} + {:ex_json_schema, "~> 0.9.1"}, + {:ueberauth, "~> 0.7"}, + {:ueberauth_auth0, "~> 2.0"}, + {:bureaucrat, "~> 0.2.9", only: :test} ] end @@ -155,7 +159,7 @@ defmodule BlockScoutWeb.Mixfile do defp package do [ - maintainers: ["POA Networks Ltd."], + maintainers: ["Blockscout"], licenses: ["GPL 3.0"], links: %{"GitHub" => "https://github.com/blockscout/blockscout"} ] diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 5f460bb96aa8..9b7bce7e34e2 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1,2679 +1,3501 @@ -#, elixir-format #: lib/block_scout_web/views/address_token_balance_view.ex:10 +#, elixir-autogen, elixir-format msgid "%{count} token" msgid_plural "%{count} tokens" msgstr[0] "" msgstr[1] "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:29 +#, elixir-autogen, elixir-format msgid "%{count} transaction" msgid_plural "%{count} transactions" msgstr[0] "" msgstr[1] "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 +#, elixir-autogen, elixir-format msgid " - minimal bytecode implementation that delegates all calls to a known address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +#, elixir-autogen, elixir-format msgid " is recommended." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:3 +#, elixir-autogen, elixir-format msgid "%{address} - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:12 +#, elixir-autogen, elixir-format msgid "%{block_type} Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:55 +#, elixir-autogen, elixir-format msgid "%{block_type} Height" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:7 +#, elixir-autogen, elixir-format msgid "%{block_type}s" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:85 +#, elixir-autogen, elixir-format msgid "%{count} Transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/chain/_block.html.eex:11 +#, elixir-autogen, elixir-format msgid "%{count} Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:2 +#, elixir-autogen, elixir-format msgid "%{subnetwork} %{network} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_default_title.html.eex:2 +#, elixir-autogen, elixir-format msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:349 +#: lib/block_scout_web/views/transaction_view.ex:347 +#, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 +#, elixir-autogen, elixir-format msgid "(query)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:223 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 +#, elixir-autogen, elixir-format +msgid ") may be added for each contract. Click the Add Library button to add an additional one." +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:236 +#, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:195 +#, elixir-autogen, elixir-format msgid "64-bit hash of value verifying proof-of-work (note: null for POA chains)." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:97 +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 +#, elixir-autogen, elixir-format msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 +#, elixir-autogen, elixir-format +msgid "A library name called in the .sol file. Multiple libraries (up to " +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 +#, elixir-autogen, elixir-format msgid "A string with the name of the action to be invoked." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 +#, elixir-autogen, elixir-format msgid "A string with the name of the module to be invoked." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "ABI" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 +#, elixir-autogen, elixir-format msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 +#, elixir-autogen, elixir-format msgid "API Documentation" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 +#, elixir-autogen, elixir-format msgid "API endpoints for the %{subnetwork}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 +#, elixir-autogen, elixir-format msgid "API for the %{subnetwork} - BlockScout" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:13 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:14 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:29 +#, elixir-autogen, elixir-format +msgid "API key" +msgstr "" + +#: lib/block_scout_web/templates/account/api_key/index.html.eex:7 +#: lib/block_scout_web/templates/account/common/_nav.html.eex:16 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 +#, elixir-autogen, elixir-format +msgid "API keys" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:100 +#, elixir-autogen, elixir-format msgid "APIs" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 +#, elixir-autogen, elixir-format msgid "Action" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 +#, elixir-autogen, elixir-format +msgid "Actions" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:425 +#, elixir-autogen, elixir-format msgid "Actual gas amount used by the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_footer.html.eex:44 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 +#: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:11 +#, elixir-autogen, elixir-format msgid "Add" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/index.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Add API key" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 +#, elixir-autogen, elixir-format +msgid "Add Contract Libraries" +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Add Custom ABI" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 +#, elixir-autogen, elixir-format +msgid "Add Library" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 +#, elixir-autogen, elixir-format +msgid "Add address" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 +#, elixir-autogen, elixir-format +msgid "Add address tag" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Add address to the Watch list" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 +#, elixir-autogen, elixir-format +msgid "Add transaction tag" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 lib/block_scout_web/views/address_view.ex:104 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:29 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:54 +#: lib/block_scout_web/views/address_view.ex:107 +#, elixir-autogen, elixir-format msgid "Address" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:213 +#: lib/block_scout_web/templates/transaction/overview.html.eex:217 +#, elixir-autogen, elixir-format msgid "Address (external or contract) receiving the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:193 +#: lib/block_scout_web/templates/transaction/overview.html.eex:199 +#, elixir-autogen, elixir-format msgid "Address (external or contract) sending the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:165 -msgid "Address balance in xDAI (doesn't include ERC20, ERC721, ERC1155 tokens)." +#: lib/block_scout_web/templates/account/common/_nav.html.eex:10 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Address Tags" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:149 +#, elixir-autogen, elixir-format +msgid "Address balance in" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:50 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 +#, elixir-autogen, elixir-format msgid "Address of the token contract" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Address used in token mintings and burnings." +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Address*" +msgstr "" + #: lib/block_scout_web/templates/address/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Addresses" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 lib/block_scout_web/templates/layout/_topnav.html.eex:71 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 lib/block_scout_web/views/address_token_transfer_view.ex:11 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:82 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 +#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 +#: lib/block_scout_web/views/address_token_transfer_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11 +#: lib/block_scout_web/views/verified_contracts_view.ex:11 +#, elixir-autogen, elixir-format msgid "All" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 +#, elixir-autogen, elixir-format msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:190 +#: lib/block_scout_web/templates/address/overview.html.eex:174 +#, elixir-autogen, elixir-format msgid "All tokens in the account and total value." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:411 +#, elixir-autogen, elixir-format msgid "Amount of" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:236 +#, elixir-autogen, elixir-format msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +#, elixir-autogen, elixir-format msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:117 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#, elixir-autogen, elixir-format msgid "Apps" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Average" +msgstr "" + #: lib/block_scout_web/templates/chain/show.html.eex:100 +#, elixir-autogen, elixir-format msgid "Average block time" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:30 -msgid "Back Home" +#: lib/block_scout_web/templates/account/api_key/form.html.eex:25 +#, elixir-autogen, elixir-format +msgid "Back to API keys (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Back to Address Tags (Cancel)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:166 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Back to Custom ABI (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Back to Transaction Tags (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 +#, elixir-autogen, elixir-format +msgid "Back to Watch list (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 +#: lib/block_scout_web/templates/address/overview.html.eex:150 #: lib/block_scout_web/templates/address_token/overview.html.eex:51 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:57 +#, elixir-autogen, elixir-format msgid "Balance" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_state/index.html.eex:35 +#, elixir-autogen, elixir-format +msgid "Balance after" +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/index.html.eex:32 +#, elixir-autogen, elixir-format +msgid "Balance before" +msgstr "" + #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 +#, elixir-autogen, elixir-format msgid "Balances" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:207 +#, elixir-autogen, elixir-format msgid "Base Fee per Gas" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Base URL:" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:446 +#, elixir-autogen, elixir-format msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 -#: lib/block_scout_web/templates/block/overview.html.eex:29 lib/block_scout_web/templates/transaction/overview.html.eex:152 +#: lib/block_scout_web/templates/block/overview.html.eex:29 +#: lib/block_scout_web/templates/transaction/overview.html.eex:158 +#, elixir-autogen, elixir-format msgid "Block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 +#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 +#, elixir-autogen, elixir-format msgid "Block #%{number}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:3 +#, elixir-autogen, elixir-format msgid "Block %{block_number} - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block_transaction/404.html.eex:7 +#, elixir-autogen, elixir-format msgid "Block Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:53 +#, elixir-autogen, elixir-format msgid "Block Height" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/app.html.eex:43 +#, elixir-autogen, elixir-format msgid "Block Mined, awaiting import..." msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:33 +#: lib/block_scout_web/views/transaction_view.ex:34 +#, elixir-autogen, elixir-format msgid "Block Pending" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:158 +#, elixir-autogen, elixir-format msgid "Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks)." msgstr "" -#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:15 +#, elixir-autogen, elixir-format msgid "Block not found, please try again later." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:151 +#: lib/block_scout_web/templates/transaction/overview.html.eex:157 +#, elixir-autogen, elixir-format msgid "Block number containing the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:273 +#: lib/block_scout_web/templates/address/overview.html.eex:257 +#, elixir-autogen, elixir-format msgid "Block number in which the address was updated." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:4 +#, elixir-autogen, elixir-format msgid "BlockScout provides analytics data, API, and Smart Contract tools for the %{subnetwork}" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:29 +#, elixir-autogen, elixir-format +msgid "Blockchain" +msgstr "" + #: lib/block_scout_web/templates/chain/show.html.eex:153 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:23 lib/block_scout_web/templates/layout/_topnav.html.eex:27 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:38 +#, elixir-autogen, elixir-format msgid "Blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/app.html.eex:42 +#, elixir-autogen, elixir-format msgid "Blocks Indexed" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:48 -#: lib/block_scout_web/templates/address/overview.html.eex:291 lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:371 +#: lib/block_scout_web/templates/address/overview.html.eex:275 +#: lib/block_scout_web/templates/address_validation/index.html.eex:11 +#: lib/block_scout_web/views/address_view.ex:381 +#, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Blocks With Internal Transactions Indexed" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:22 +#, elixir-autogen, elixir-format msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Burn address" +msgstr "" + #: lib/block_scout_web/templates/block/_tile.html.eex:64 #: lib/block_scout_web/templates/block/overview.html.eex:216 +#, elixir-autogen, elixir-format msgid "Burnt Fees" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:65 +#, elixir-autogen, elixir-format msgid "CRC Worth" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2 +#, elixir-autogen, elixir-format msgid "CSV" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 #: lib/block_scout_web/views/internal_transaction_view.ex:21 +#, elixir-autogen, elixir-format msgid "Call" msgstr "" -#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:22 +#, elixir-autogen, elixir-format msgid "Call Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:105 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:145 +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:62 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 +#, elixir-autogen, elixir-format msgid "Cancel" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 -msgid "Change Network" +#: lib/block_scout_web/templates/transaction_state/index.html.eex:38 +#, elixir-autogen, elixir-format +msgid "Change" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:41 +#, elixir-autogen, elixir-format msgid "Chat (#blockscout)" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:63 +#: lib/block_scout_web/views/block_view.ex:65 +#, elixir-autogen, elixir-format msgid "Chore Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 +#, elixir-autogen, elixir-format msgid "Clear" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 +#, elixir-autogen, elixir-format msgid "Close" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:58 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:364 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 +#: lib/block_scout_web/views/address_view.ex:374 +#, elixir-autogen, elixir-format msgid "Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:370 +#: lib/block_scout_web/views/address_view.ex:380 +#, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format msgid "Collapse" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Company name" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:32 +#, elixir-autogen, elixir-format +msgid "Company website" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 +#, elixir-autogen, elixir-format msgid "Compiler" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:65 +#: lib/block_scout_web/templates/address_contract/index.html.eex:137 +#, elixir-autogen, elixir-format +msgid "Compiler Settings" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:66 +#, elixir-autogen, elixir-format msgid "Compiler version" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:342 +#: lib/block_scout_web/views/transaction_view.ex:340 +#, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:118 +#: lib/block_scout_web/templates/transaction/overview.html.eex:124 +#, elixir-autogen, elixir-format msgid "Confirmed by " msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:184 +#: lib/block_scout_web/templates/transaction/overview.html.eex:190 +#, elixir-autogen, elixir-format msgid "Confirmed within" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 lib/block_scout_web/templates/tokens/holder/index.html.eex:15 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 +#, elixir-autogen, elixir-format msgid "Connection Lost" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/block/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer internal transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 lib/block_scout_web/templates/transaction/index.html.eex:22 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 +#: lib/block_scout_web/templates/transaction/index.html.eex:22 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:10 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer validations" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:90 +#: lib/block_scout_web/templates/address_contract/index.html.eex:91 +#, elixir-autogen, elixir-format msgid "Constructor Arguments" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 -#: lib/block_scout_web/templates/transaction/overview.html.eex:223 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 +#, elixir-autogen, elixir-format +msgid "Constructor args" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 +#: lib/block_scout_web/templates/transaction/overview.html.eex:227 +#, elixir-autogen, elixir-format msgid "Contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:122 +#: lib/block_scout_web/templates/address_contract/index.html.eex:152 +#, elixir-autogen, elixir-format msgid "Contract ABI" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 lib/block_scout_web/views/address_view.ex:102 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 +#: lib/block_scout_web/views/address_view.ex:105 +#, elixir-autogen, elixir-format msgid "Contract Address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:42 lib/block_scout_web/views/address_view.ex:76 +#: lib/block_scout_web/views/address_view.ex:45 +#: lib/block_scout_web/views/address_view.ex:79 +#, elixir-autogen, elixir-format msgid "Contract Address Pending" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:457 +#: lib/block_scout_web/views/transaction_view.ex:455 +#, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:454 +#: lib/block_scout_web/views/transaction_view.ex:452 +#, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:138 -#: lib/block_scout_web/templates/address_contract/index.html.eex:153 +#: lib/block_scout_web/templates/address_contract/index.html.eex:169 +#: lib/block_scout_web/templates/address_contract/index.html.eex:184 +#, elixir-autogen, elixir-format msgid "Contract Creation Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 +#, elixir-autogen, elixir-format msgid "Contract Libraries" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:93 +#: lib/block_scout_web/templates/address/overview.html.eex:75 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 +#, elixir-autogen, elixir-format msgid "Contract Name" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:25 +#: lib/block_scout_web/templates/address_contract/index.html.eex:26 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 +#, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:57 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Contract name or address" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:58 +#, elixir-autogen, elixir-format msgid "Contract name:" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:100 +#: lib/block_scout_web/templates/address_contract/index.html.eex:101 +#, elixir-autogen, elixir-format msgid "Contract source code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:144 +#: lib/block_scout_web/templates/address/overview.html.eex:120 +#, elixir-autogen, elixir-format +msgid "Contract was precompiled and created at genesis or contract creation transaction is missing" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Contracts" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:175 +#, elixir-autogen, elixir-format msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:40 +#, elixir-autogen, elixir-format msgid "Contribute" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:124 +#: lib/block_scout_web/templates/address_contract/index.html.eex:154 +#, elixir-autogen, elixir-format msgid "Copy ABI" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:38 lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:105 lib/block_scout_web/templates/tokens/overview/_details.html.eex:42 +#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 +#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Copy API key" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 +#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 +#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 +#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 +#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 +#: lib/block_scout_web/templates/address/overview.html.eex:38 +#: lib/block_scout_web/templates/address/overview.html.eex:39 +#: lib/block_scout_web/templates/block/overview.html.eex:104 +#: lib/block_scout_web/templates/block/overview.html.eex:105 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 +#, elixir-autogen, elixir-format msgid "Copy Address" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:140 -#: lib/block_scout_web/templates/address_contract/index.html.eex:156 +#: lib/block_scout_web/templates/address_contract/index.html.eex:139 +#, elixir-autogen, elixir-format +msgid "Copy Compiler Settings" +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Copy Contract Address" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:171 +#: lib/block_scout_web/templates/address_contract/index.html.eex:187 +#, elixir-autogen, elixir-format msgid "Copy Contract Creation Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:19 +#, elixir-autogen, elixir-format msgid "Copy Decompiled Contract Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:183 -#: lib/block_scout_web/templates/address_contract/index.html.eex:193 +#: lib/block_scout_web/templates/address_contract/index.html.eex:208 +#: lib/block_scout_web/templates/address_contract/index.html.eex:218 +#, elixir-autogen, elixir-format msgid "Copy Deployed ByteCode" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:14 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:15 lib/block_scout_web/templates/transaction/overview.html.eex:203 -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 +#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 +#: lib/block_scout_web/templates/transaction/overview.html.eex:207 +#: lib/block_scout_web/templates/transaction/overview.html.eex:208 +#, elixir-autogen, elixir-format msgid "Copy From Address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:130 +#, elixir-autogen, elixir-format msgid "Copy Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 +#, elixir-autogen, elixir-format msgid "Copy Metadata" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:150 +#, elixir-autogen, elixir-format msgid "Copy Parent Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15 +#, elixir-autogen, elixir-format msgid "Copy Raw Trace" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:102 -#: lib/block_scout_web/templates/address_contract/index.html.eex:113 +#: lib/block_scout_web/templates/address_contract/index.html.eex:115 +#: lib/block_scout_web/templates/address_contract/index.html.eex:127 +#, elixir-autogen, elixir-format msgid "Copy Source Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:31 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:32 lib/block_scout_web/templates/transaction/overview.html.eex:232 -#: lib/block_scout_web/templates/transaction/overview.html.eex:233 lib/block_scout_web/templates/transaction/overview.html.eex:242 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 +#: lib/block_scout_web/templates/transaction/overview.html.eex:234 +#: lib/block_scout_web/templates/transaction/overview.html.eex:235 +#: lib/block_scout_web/templates/transaction/overview.html.eex:242 #: lib/block_scout_web/templates/transaction/overview.html.eex:243 +#, elixir-autogen, elixir-format msgid "Copy To Address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 +#, elixir-autogen, elixir-format msgid "Copy Token ID" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:81 +#: lib/block_scout_web/templates/transaction/overview.html.eex:87 +#, elixir-autogen, elixir-format msgid "Copy Transaction Hash" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:82 +#: lib/block_scout_web/templates/transaction/overview.html.eex:88 +#, elixir-autogen, elixir-format msgid "Copy Txn Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:472 +#, elixir-autogen, elixir-format msgid "Copy Txn Hex Input" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:478 +#, elixir-autogen, elixir-format msgid "Copy Txn UTF-8 Input" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 lib/block_scout_web/templates/transaction/overview.html.eex:471 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 +#: lib/block_scout_web/templates/transaction/overview.html.eex:471 #: lib/block_scout_web/templates/transaction/overview.html.eex:477 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:14 +#, elixir-autogen, elixir-format msgid "Copy Value" msgstr "" -#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:25 +#, elixir-autogen, elixir-format msgid "Create" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "Create a Custom ABI to interact with contracts." +msgstr "" + +#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "Create an API key to use with your RPC и EthRPC API requests." +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:26 +#, elixir-autogen, elixir-format msgid "Create2" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:120 +#: lib/block_scout_web/templates/address/overview.html.eex:102 +#, elixir-autogen, elixir-format msgid "Creator" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 +#, elixir-autogen, elixir-format msgid "Curl" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:91 +#: lib/block_scout_web/templates/transaction/overview.html.eex:97 +#, elixir-autogen, elixir-format msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Custom" +msgstr "" + +#: lib/block_scout_web/templates/account/common/_nav.html.eex:19 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Custom ABI" +msgstr "" + +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 +#, elixir-autogen, elixir-format +msgid "Custom ABI from account" +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:69 +#, elixir-autogen, elixir-format +msgid "Daily Transactions" +msgstr "" + #: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 +#, elixir-autogen, elixir-format msgid "Data" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:70 +#, elixir-autogen, elixir-format msgid "Date & time at which block was produced." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:170 +#: lib/block_scout_web/templates/transaction/overview.html.eex:176 +#, elixir-autogen, elixir-format msgid "Date & time of transaction inclusion, including length of time for confirmation." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 +#, elixir-autogen, elixir-format msgid "Decimals" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:34 lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57 lib/block_scout_web/templates/transaction_log/_logs.html.eex:73 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:34 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73 +#, elixir-autogen, elixir-format msgid "Decoded" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/address_view.ex:365 +#: lib/block_scout_web/views/address_view.ex:375 +#, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:75 +#, elixir-autogen, elixir-format msgid "Decompiled code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "Decompiled contract code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:10 +#, elixir-autogen, elixir-format msgid "Decompiler version" msgstr "" -#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:23 +#, elixir-autogen, elixir-format msgid "Delegate Call" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 -#: lib/block_scout_web/templates/address_contract/index.html.eex:189 +#: lib/block_scout_web/templates/address_contract/index.html.eex:206 +#: lib/block_scout_web/templates/address_contract/index.html.eex:214 +#, elixir-autogen, elixir-format msgid "Deployed ByteCode" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 +#, elixir-autogen, elixir-format msgid "Description" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:56 +#, elixir-autogen, elixir-format +msgid "Description*" +msgstr "" + #: lib/block_scout_web/templates/address/overview.html.eex:30 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 +#, elixir-autogen, elixir-format msgid "Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:159 +#, elixir-autogen, elixir-format msgid "Difficulty" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:145 +#: lib/block_scout_web/templates/address_contract/index.html.eex:176 +#, elixir-autogen, elixir-format msgid "Displaying the init data provided of the creating transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/csv_export/index.html.eex:25 +#: lib/block_scout_web/templates/csv_export/index.html.eex:24 +#, elixir-autogen, elixir-format msgid "Download" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 +#, elixir-autogen, elixir-format +msgid "Drop all Solidity contract source files into the drop zone." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 +#, elixir-autogen, elixir-format +msgid "Drop all Solidity or Yul contract source files into the drop zone." +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 +#, elixir-autogen, elixir-format msgid "Drop sources and metadata JSON file or click here" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 +#, elixir-autogen, elixir-format +msgid "Drop sources or click here" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 +#, elixir-autogen, elixir-format msgid "Drop the standard input JSON file or click here" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:22 -msgid "During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:27 +#, elixir-autogen, elixir-format +msgid "E-mail*" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 +#, elixir-autogen, elixir-format msgid "EIP-1167" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:214 +#: lib/block_scout_web/views/transaction_view.ex:212 +#, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:212 +#: lib/block_scout_web/views/transaction_view.ex:210 +#, elixir-autogen, elixir-format msgid "ERC-20 " msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:213 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 +#, elixir-autogen, elixir-format +msgid "ERC-20 tokens (beta)" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:211 +#, elixir-autogen, elixir-format msgid "ERC-721 " msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_token/overview.html.eex:1 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:104 lib/block_scout_web/templates/smart_contract/_functions.html.eex:104 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:146 -msgid "ETH" +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 +#, elixir-autogen, elixir-format +msgid "ERC-721, ERC-1155 tokens (NFT) (beta)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +#, elixir-autogen, elixir-format msgid "ETH RPC API Documentation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 +#: lib/block_scout_web/templates/address_contract/index.html.eex:77 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 +#, elixir-autogen, elixir-format msgid "EVM Version" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 +#, elixir-autogen, elixir-format +msgid "EVM version details" +msgstr "" + #: lib/block_scout_web/views/block_transaction_view.ex:7 +#, elixir-autogen, elixir-format msgid "Easy Cowboy! This block does not exist yet!" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/row.html.eex:16 +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 +#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 +#, elixir-autogen, elixir-format +msgid "Edit" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Edit Watch list address" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 +#, elixir-autogen, elixir-format +msgid "Email notifications" +msgstr "" + #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 +#, elixir-autogen, elixir-format msgid "Emission Contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:71 +#: lib/block_scout_web/views/block_view.ex:73 +#, elixir-autogen, elixir-format msgid "Emission Reward" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:68 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 +#, elixir-autogen, elixir-format msgid "Enter the Solidity Contract Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 +#, elixir-autogen, elixir-format msgid "Enter the Vyper Contract Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 +#, elixir-autogen, elixir-format msgid "Error" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Error in internal transactions" +msgstr "" + #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 +#, elixir-autogen, elixir-format msgid "Error rendering value" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 +#, elixir-autogen, elixir-format msgid "Error trying to fetch balances." msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:353 +#: lib/block_scout_web/views/transaction_view.ex:351 +#, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:351 +#: lib/block_scout_web/views/transaction_view.ex:349 +#, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:137 +#: lib/block_scout_web/templates/address/overview.html.eex:120 +#, elixir-autogen, elixir-format msgid "Error: Could not determine contract creator." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:103 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#, elixir-autogen, elixir-format msgid "Eth RPC" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/_current_coin_balance.html.eex:11 -#: lib/block_scout_web/templates/address/index.html.eex:5 lib/block_scout_web/templates/address/overview.html.eex:179 -#: lib/block_scout_web/templates/block/overview.html.eex:215 lib/block_scout_web/templates/internal_transaction/_tile.html.eex:24 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:76 lib/block_scout_web/templates/layout/app.html.eex:48 -#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 lib/block_scout_web/templates/transaction/_tile.html.eex:43 -#: lib/block_scout_web/templates/transaction/overview.html.eex:411 lib/block_scout_web/views/wei_helpers.ex:78 -msgid "Ether" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 +#, elixir-autogen, elixir-format msgid "Example Value" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 +#, elixir-autogen, elixir-format msgid "Execute" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format msgid "Expand" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/csv_export/index.html.eex:10 +#, elixir-autogen, elixir-format msgid "Export Data" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:224 +#: lib/block_scout_web/templates/address_contract/index.html.eex:243 +#, elixir-autogen, elixir-format msgid "External libraries" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 +#, elixir-autogen, elixir-format msgid "Failed to decode input data." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:37 +#, elixir-autogen, elixir-format msgid "Failed to decode log data." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:24 -msgid "Favorites" +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Fast" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:263 +#: lib/block_scout_web/templates/address/overview.html.eex:247 +#, elixir-autogen, elixir-format msgid "Fetching gas used..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:111 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 +#, elixir-autogen, elixir-format msgid "Fetching holders..." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 +#, elixir-autogen, elixir-format msgid "Fetching tokens..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:210 -#: lib/block_scout_web/templates/address/overview.html.eex:218 +#: lib/block_scout_web/templates/address/overview.html.eex:194 +#: lib/block_scout_web/templates/address/overview.html.eex:202 +#, elixir-autogen, elixir-format msgid "Fetching transactions..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:237 -#: lib/block_scout_web/templates/address/overview.html.eex:245 lib/block_scout_web/templates/tokens/overview/_details.html.eex:122 +#: lib/block_scout_web/templates/address/overview.html.eex:221 +#: lib/block_scout_web/templates/address/overview.html.eex:229 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 +#, elixir-autogen, elixir-format msgid "Fetching transfers..." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Filter by compiler:" +msgstr "" + #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 +#, elixir-autogen, elixir-format msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:33 +#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "For contract" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 +#, elixir-autogen, elixir-format msgid "Forked Blocks (Reorgs)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_footer.html.eex:42 +#: lib/block_scout_web/templates/layout/_footer.html.eex:43 +#, elixir-autogen, elixir-format msgid "Forum" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_token_transfer_view.ex:10 lib/block_scout_web/views/address_transaction_view.ex:10 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:34 +#: lib/block_scout_web/templates/transaction/overview.html.eex:200 +#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 +#: lib/block_scout_web/views/address_token_transfer_view.ex:10 +#: lib/block_scout_web/views/address_transaction_view.ex:10 +#, elixir-autogen, elixir-format msgid "From" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 +#, elixir-autogen, elixir-format msgid "GET" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:67 -#: lib/block_scout_web/templates/block/overview.html.eex:187 lib/block_scout_web/templates/transaction/overview.html.eex:373 +#: lib/block_scout_web/templates/block/overview.html.eex:187 +#: lib/block_scout_web/templates/transaction/overview.html.eex:373 +#, elixir-autogen, elixir-format msgid "Gas Limit" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:353 +#, elixir-autogen, elixir-format msgid "Gas Price" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:256 -#: lib/block_scout_web/templates/block/_tile.html.eex:73 lib/block_scout_web/templates/block/overview.html.eex:178 +#: lib/block_scout_web/templates/address/overview.html.eex:240 +#: lib/block_scout_web/templates/block/_tile.html.eex:73 +#: lib/block_scout_web/templates/block/overview.html.eex:178 +#, elixir-autogen, elixir-format msgid "Gas Used" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#, elixir-autogen, elixir-format msgid "Gas Used by Transaction" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:255 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Gas tracker" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:239 +#, elixir-autogen, elixir-format msgid "Gas used by the address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:60 +#, elixir-autogen, elixir-format msgid "Genesis Block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:24 +#, elixir-autogen, elixir-format msgid "Github" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 +#, elixir-autogen, elixir-format msgid "Go to" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:93 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:104 +#, elixir-autogen, elixir-format msgid "GraphQL" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 -#: lib/block_scout_web/views/block_view.ex:21 lib/block_scout_web/views/wei_helpers.ex:77 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 +#: lib/block_scout_web/views/block_view.ex:22 +#: lib/block_scout_web/views/wei_helpers.ex:77 +#, elixir-autogen, elixir-format msgid "Gwei" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:123 +#, elixir-autogen, elixir-format msgid "Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:454 #: lib/block_scout_web/templates/transaction/overview.html.eex:458 +#, elixir-autogen, elixir-format msgid "Hex (Default)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 +#, elixir-autogen, elixir-format msgid "Holders" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +#, elixir-autogen, elixir-format msgid "However, in general, the" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 +#, elixir-autogen, elixir-format msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:86 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 +#, elixir-autogen, elixir-format msgid "IN" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:26 -msgid "If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 +#, elixir-autogen, elixir-format +msgid "If you enabled optimization during compilation, select yes." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:12 -msgid "If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:149 +#: lib/block_scout_web/templates/address/overview.html.eex:133 +#, elixir-autogen, elixir-format msgid "Implementation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:148 +#: lib/block_scout_web/templates/address/overview.html.eex:132 +#, elixir-autogen, elixir-format msgid "Implementation address of the proxy contract." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Include nightly builds" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 +#, elixir-autogen, elixir-format +msgid "Incoming" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:438 +#, elixir-autogen, elixir-format msgid "Index position of Transaction in the block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:249 +#, elixir-autogen, elixir-format msgid "Index position(s) of referenced stale blocks." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 +#, elixir-autogen, elixir-format msgid "Indexed?" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:44 -msgid "Indexing Tokens" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 +#, elixir-autogen, elixir-format msgid "Input" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:215 +#: lib/block_scout_web/templates/transaction/overview.html.eex:219 +#, elixir-autogen, elixir-format msgid "Interacted With (To)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 +#, elixir-autogen, elixir-format msgid "Internal Transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:28 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 lib/block_scout_web/views/address_view.ex:361 -#: lib/block_scout_web/views/transaction_view.ex:512 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 +#: lib/block_scout_web/views/address_view.ex:371 +#: lib/block_scout_web/views/transaction_view.ex:510 +#, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/invalid.html.eex:6 -msgid "Invalid Transaction Hash" +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 +#: lib/block_scout_web/views/tokens/overview_view.ex:42 +#, elixir-autogen, elixir-format +msgid "Inventory" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:15 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 lib/block_scout_web/views/tokens/overview_view.ex:42 -msgid "Inventory" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Is Yul contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:16 -msgid "It could still be in the TX Pool of a different node, waiting to be broadcasted." +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 +#, elixir-autogen, elixir-format +msgid "Last 24h" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:274 +#: lib/block_scout_web/templates/address/overview.html.eex:258 +#, elixir-autogen, elixir-format msgid "Last Balance Update" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:45 -msgid "Less than" +#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Learn more" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:3 -msgid "Library Address" +#: lib/block_scout_web/templates/layout/app.html.eex:45 +#, elixir-autogen, elixir-format +msgid "Less than" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:3 -msgid "Library Name" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 +#, elixir-autogen, elixir-format +msgid "Library" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 +#, elixir-autogen, elixir-format msgid "License Expires" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 +#, elixir-autogen, elixir-format msgid "License ID" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:305 +#, elixir-autogen, elixir-format msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:289 +#, elixir-autogen, elixir-format msgid "List of token burnt in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:272 +#, elixir-autogen, elixir-format msgid "List of token minted in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:256 +#, elixir-autogen, elixir-format msgid "List of token transferred in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 +#, elixir-autogen, elixir-format msgid "Loading chart..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:70 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:139 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 lib/block_scout_web/templates/address_read_contract/index.html.eex:12 -#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 lib/block_scout_web/templates/address_write_contract/index.html.eex:12 -#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 lib/block_scout_web/templates/tokens/contract/index.html.eex:16 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 +#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 +#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 +#: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "Loading..." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 +#, elixir-autogen, elixir-format msgid "Log Data" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:131 +#, elixir-autogen, elixir-format msgid "Log Index" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:41 -#: lib/block_scout_web/templates/address_logs/index.html.eex:10 lib/block_scout_web/templates/transaction/_tabs.html.eex:17 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 lib/block_scout_web/views/address_view.ex:372 -#: lib/block_scout_web/views/transaction_view.ex:513 +#: lib/block_scout_web/templates/address_logs/index.html.eex:10 +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 +#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 +#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/transaction_view.ex:511 +#, elixir-autogen, elixir-format msgid "Logs" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:52 +#, elixir-autogen, elixir-format msgid "Main Networks" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 -msgid "Mainnet" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:52 -#: lib/block_scout_web/templates/layout/app.html.eex:46 lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 -#: lib/block_scout_web/views/address_view.ex:142 +#: lib/block_scout_web/templates/layout/app.html.eex:46 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 +#: lib/block_scout_web/views/address_view.ex:145 +#, elixir-autogen, elixir-format msgid "Market Cap" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 +#, elixir-autogen, elixir-format +msgid "Market cap" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:382 +#, elixir-autogen, elixir-format msgid "Max Fee per Gas" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:392 +#, elixir-autogen, elixir-format msgid "Max Priority Fee per Gas" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:319 +#: lib/block_scout_web/views/transaction_view.ex:317 +#, elixir-autogen, elixir-format msgid "Max of" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:372 +#, elixir-autogen, elixir-format msgid "Maximum gas amount approved for the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:381 +#, elixir-autogen, elixir-format msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:196 +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:198 +#, elixir-autogen, elixir-format msgid "Metadata" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 +#, elixir-autogen, elixir-format msgid "Method Id" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/overview.html.eex:98 lib/block_scout_web/templates/chain/_block.html.eex:16 +#: lib/block_scout_web/templates/block/overview.html.eex:98 +#: lib/block_scout_web/templates/chain/_block.html.eex:16 +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:23 +#, elixir-autogen, elixir-format msgid "Miner" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:61 -#: lib/block_scout_web/views/block_view.ex:66 +#: lib/block_scout_web/views/block_view.ex:63 +#: lib/block_scout_web/views/block_view.ex:68 +#, elixir-autogen, elixir-format msgid "Miner Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 +#, elixir-autogen, elixir-format msgid "Minimal Proxy Contract for" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:206 +#, elixir-autogen, elixir-format msgid "Minimum fee required per unit of gas. Fee adjusts based on network congestion." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 +#, elixir-autogen, elixir-format msgid "Model" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 +#, elixir-autogen, elixir-format msgid "Module" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 +#, elixir-autogen, elixir-format msgid "More internal transactions have come in" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:216 lib/block_scout_web/templates/pending_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/chain/show.html.eex:216 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 +#, elixir-autogen, elixir-format msgid "More transactions have come in" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 +#, elixir-autogen, elixir-format msgid "Must be set to:" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:58 +#, elixir-autogen, elixir-format +msgid "N/A" +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:116 +#, elixir-autogen, elixir-format msgid "N/A bytes" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/form.html.eex:19 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:28 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 +#, elixir-autogen, elixir-format msgid "Name" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/form.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Name this API key" +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Name this Custom ABI" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Name this address" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Name this transaction" +msgstr "" + #: lib/block_scout_web/templates/address_token/overview.html.eex:44 +#, elixir-autogen, elixir-format msgid "Net Worth" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 +#, elixir-autogen, elixir-format msgid "New Smart Contract Verification" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 +#, elixir-autogen, elixir-format msgid "New Solidity Smart Contract Verification" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 +#, elixir-autogen, elixir-format +msgid "New Solidity/Yul Smart Contract Verification" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 +#, elixir-autogen, elixir-format msgid "New Vyper Smart Contract Verification" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:73 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 lib/block_scout_web/templates/address_contract_verification/new.html.eex:88 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 +#, elixir-autogen, elixir-format msgid "Next" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:42 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 +#, elixir-autogen, elixir-format msgid "No" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:23 +#, elixir-autogen, elixir-format +msgid "No trace entries found." +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:196 #: lib/block_scout_web/templates/transaction/overview.html.eex:436 +#, elixir-autogen, elixir-format msgid "Nonce" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +#, elixir-autogen, elixir-format msgid "Not unique Token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#, elixir-autogen, elixir-format msgid "Number of accounts holding the token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:290 +#: lib/block_scout_web/templates/address/overview.html.eex:274 +#, elixir-autogen, elixir-format msgid "Number of blocks validated by this validator." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:129 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 +#, elixir-autogen, elixir-format msgid "Number of digits that come after the decimal place when displaying token value" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:201 +#: lib/block_scout_web/templates/address/overview.html.eex:185 +#, elixir-autogen, elixir-format msgid "Number of transactions related to this address." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:117 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 +#, elixir-autogen, elixir-format msgid "Number of transfers for the token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: lib/block_scout_web/templates/address/overview.html.eex:212 +#, elixir-autogen, elixir-format msgid "Number of transfers to/from this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:82 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 +#, elixir-autogen, elixir-format msgid "OUT" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format msgid "Only the first" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:61 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 +#, elixir-autogen, elixir-format +msgid "Optimization" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:62 +#, elixir-autogen, elixir-format msgid "Optimization enabled" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:70 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:58 +#: lib/block_scout_web/templates/address_contract/index.html.eex:71 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 +#, elixir-autogen, elixir-format msgid "Optimization runs" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:76 +#, elixir-autogen, elixir-format msgid "Other Explorers" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 +#, elixir-autogen, elixir-format +msgid "Outgoing" +msgstr "" + #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 +#, elixir-autogen, elixir-format msgid "Owner Address" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 +#, elixir-autogen, elixir-format +msgid "POA solidity flattener or the" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 +#, elixir-autogen, elixir-format msgid "POST" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format msgid "Page" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/page_not_found/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Page not found" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 +#, elixir-autogen, elixir-format msgid "Parameters" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:139 +#, elixir-autogen, elixir-format msgid "Parent Hash" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:54 -#: lib/block_scout_web/views/transaction_view.ex:348 lib/block_scout_web/views/transaction_view.ex:386 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:62 +#: lib/block_scout_web/views/transaction_view.ex:346 +#: lib/block_scout_web/views/transaction_view.ex:384 +#, elixir-autogen, elixir-format msgid "Pending" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Pending Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 +#, elixir-autogen, elixir-format msgid "Play" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 +#, elixir-autogen, elixir-format +msgid "Please select notification methods:" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Please select what types of notifications you will receive:" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:438 +#, elixir-autogen, elixir-format msgid "Position" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:254 +#, elixir-autogen, elixir-format msgid "Position %{index}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 +#, elixir-autogen, elixir-format msgid "Potential matches from our contract method database:" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:27 +#, elixir-autogen, elixir-format msgid "Press / and focus will be moved to the search field" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:41 -#: lib/block_scout_web/templates/layout/app.html.eex:47 lib/block_scout_web/templates/tokens/overview/_details.html.eex:94 +#: lib/block_scout_web/templates/layout/app.html.eex:47 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 +#, elixir-autogen, elixir-format msgid "Price" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:93 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:94 +#, elixir-autogen, elixir-format msgid "Price per token on the exchanges" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:352 +#, elixir-autogen, elixir-format msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:225 #: lib/block_scout_web/templates/transaction/overview.html.eex:402 +#, elixir-autogen, elixir-format msgid "Priority Fee / Tip" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:62 +#, elixir-autogen, elixir-format msgid "Priority Fees" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/common/_nav.html.eex:4 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Profile" +msgstr "" + +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Public Tags" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Public tag" +msgstr "" + +#: lib/block_scout_web/templates/account/common/_nav.html.eex:22 +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Public tags" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:50 +#, elixir-autogen, elixir-format +msgid "Public tags* (2 tags maximum, please use \";\" as a divider)" +msgstr "" + #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 +#, elixir-autogen, elixir-format msgid "QR Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:109 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format msgid "Query" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:98 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#, elixir-autogen, elixir-format msgid "RPC" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:447 +#, elixir-autogen, elixir-format msgid "Raw Input" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 -#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:514 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 +#: lib/block_scout_web/views/transaction_view.ex:512 +#, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:81 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 lib/block_scout_web/views/address_view.ex:366 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 +#: lib/block_scout_web/views/address_view.ex:376 #: lib/block_scout_web/views/tokens/overview_view.ex:41 +#, elixir-autogen, elixir-format msgid "Read Contract" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:88 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 lib/block_scout_web/views/address_view.ex:367 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 +#: lib/block_scout_web/views/address_view.ex:377 +#, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format msgid "Records" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/row.html.eex:13 +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Remove" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 +#, elixir-autogen, elixir-format +msgid "Remove from Watch list" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 +#, elixir-autogen, elixir-format msgid "Request URL" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:142 +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Request a public tag/label" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:37 +#, elixir-autogen, elixir-format +msgid "Request to add public tag" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Request to edit a public tag/label" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 +#, elixir-autogen, elixir-format msgid "Reset" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 +#, elixir-autogen, elixir-format msgid "Response Body" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 +#, elixir-autogen, elixir-format msgid "Responses" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:92 +#: lib/block_scout_web/templates/transaction/overview.html.eex:98 +#, elixir-autogen, elixir-format msgid "Result" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:129 +#: lib/block_scout_web/templates/transaction/overview.html.eex:135 +#, elixir-autogen, elixir-format msgid "Revert reason" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/chain/_block.html.eex:27 lib/block_scout_web/views/internal_transaction_view.ex:28 +#: lib/block_scout_web/templates/chain/_block.html.eex:27 +#: lib/block_scout_web/views/internal_transaction_view.ex:28 +#, elixir-autogen, elixir-format msgid "Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 +#, elixir-autogen, elixir-format msgid "Run" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/form.html.eex:26 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 +#, elixir-autogen, elixir-format +msgid "Save" +msgstr "" + #: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/layout/_search.html.eex:34 +#, elixir-autogen, elixir-format msgid "Search" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/search/results.html.eex:17 +#, elixir-autogen, elixir-format msgid "Search Results" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:3 +#, elixir-autogen, elixir-format msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:18 -msgid "Search network" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 +#, elixir-autogen, elixir-format msgid "Search tokens" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Select Yes if you want to vefify Yul contract." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Select yes if you want to show nightly builds." +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:27 +#, elixir-autogen, elixir-format msgid "Self-Destruct" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:63 +#, elixir-autogen, elixir-format +msgid "Send request" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 +#, elixir-autogen, elixir-format msgid "Server Response" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 +#, elixir-autogen, elixir-format msgid "Show" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 +#, elixir-autogen, elixir-format msgid "Show QR Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:47 -msgid "Show Validator Info" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format msgid "Shows the current" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:59 +#, elixir-autogen, elixir-format msgid "Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:66 +#, elixir-autogen, elixir-format msgid "Shows the total CRC balance in the address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:45 +#, elixir-autogen, elixir-format msgid "Shows total assets held in the address" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Sign out" +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:114 +#, elixir-autogen, elixir-format msgid "Size" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:113 +#, elixir-autogen, elixir-format msgid "Size of the block in bytes." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Slow" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Smart contract / Address" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:4 +#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Smart contract / Address (0x...)" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 +#: lib/block_scout_web/views/verified_contracts_view.ex:9 +#, elixir-autogen, elixir-format +msgid "Solidity" +msgstr "" + #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_token/index.html.eex:60 lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 lib/block_scout_web/templates/chain/show.html.eex:157 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 lib/block_scout_web/templates/tokens/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:22 lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 +#: lib/block_scout_web/templates/address_logs/index.html.eex:23 +#: lib/block_scout_web/templates/address_token/index.html.eex:60 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 +#: lib/block_scout_web/templates/address_validation/index.html.eex:20 +#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/chain/show.html.eex:157 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 +#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 #: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 lib/block_scout_web/templates/transaction_log/index.html.eex:15 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/transaction_log/index.html.eex:15 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:8 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:45 +#, elixir-autogen, elixir-format msgid "Something went wrong, click to reload." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:222 +#, elixir-autogen, elixir-format msgid "Something went wrong, click to retry." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:7 -msgid "Sorry, We are unable to locate this transaction Hash" +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 +#, elixir-autogen, elixir-format +msgid "Sources *.sol files" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 +#, elixir-autogen, elixir-format +msgid "Sources *.sol or *.yul files" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 +#, elixir-autogen, elixir-format msgid "Sources and Metadata JSON" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:119 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:130 +#, elixir-autogen, elixir-format msgid "Stakes" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 +#, elixir-autogen, elixir-format msgid "Standard Input JSON" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:6 +#: lib/block_scout_web/views/transaction_view.ex:513 +#, elixir-autogen, elixir-format +msgid "State changes" +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:24 +#, elixir-autogen, elixir-format msgid "Static Call" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:104 +#: lib/block_scout_web/templates/transaction/overview.html.eex:110 +#, elixir-autogen, elixir-format msgid "Status" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Submission date" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:39 +#, elixir-autogen, elixir-format msgid "Submit an Issue" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:350 +#: lib/block_scout_web/views/transaction_view.ex:348 +#, elixir-autogen, elixir-format msgid "Success" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:46 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 +#, elixir-autogen, elixir-format msgid "TX Fee" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:30 +#, elixir-autogen, elixir-format msgid "Telegram" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:65 +#, elixir-autogen, elixir-format msgid "Test Networks" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 -msgid "Testnet" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 +#, elixir-autogen, elixir-format +msgid "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 +#, elixir-autogen, elixir-format +msgid "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:122 +#, elixir-autogen, elixir-format msgid "The SHA256 hash of the block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:51 +#, elixir-autogen, elixir-format msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:13 +#, elixir-autogen, elixir-format +msgid "The changes from this transaction have not yet happened since the transaction is still pending." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 +#, elixir-autogen, elixir-format +msgid "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:38 +#, elixir-autogen, elixir-format msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:138 +#, elixir-autogen, elixir-format msgid "The hash of the block from which this block was generated." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:92 +#: lib/block_scout_web/templates/address/overview.html.eex:74 +#, elixir-autogen, elixir-format msgid "The name found in the source code of the Contract." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:103 +#: lib/block_scout_web/templates/address/overview.html.eex:85 +#, elixir-autogen, elixir-format msgid "The name of the validator." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:79 +#, elixir-autogen, elixir-format msgid "The number of transactions in the block." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:40 +#, elixir-autogen, elixir-format msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:128 +#: lib/block_scout_web/templates/transaction/overview.html.eex:134 +#, elixir-autogen, elixir-format msgid "The revert reason of the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:103 +#: lib/block_scout_web/templates/transaction/overview.html.eex:109 +#, elixir-autogen, elixir-format msgid "The status of the transaction: Confirmed or Unconfirmed." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 +#, elixir-autogen, elixir-format msgid "The total amount of tokens issued" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:177 +#, elixir-autogen, elixir-format msgid "The total gas amount used in the block and its percentage of gas filled in the block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:16 +#, elixir-autogen, elixir-format msgid "There are no blocks validated by this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "There are no blocks." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:28 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 +#, elixir-autogen, elixir-format msgid "There are no holders for this Token." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 +#, elixir-autogen, elixir-format msgid "There are no internal transactions for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "There are no internal transactions for this transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "There are no logs for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_log/index.html.eex:20 +#, elixir-autogen, elixir-format msgid "There are no logs for this transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 +#, elixir-autogen, elixir-format msgid "There are no pending transactions." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 +#, elixir-autogen, elixir-format msgid "There are no token transfers for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 +#, elixir-autogen, elixir-format msgid "There are no token transfers for this transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/index.html.eex:65 +#, elixir-autogen, elixir-format msgid "There are no tokens for this address." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27 +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "There are no tokens." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:55 +#, elixir-autogen, elixir-format msgid "There are no transactions for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block_transaction/index.html.eex:27 +#, elixir-autogen, elixir-format msgid "There are no transactions for this block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:31 +#, elixir-autogen, elixir-format msgid "There are no transactions." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 lib/block_scout_web/templates/tokens/transfer/index.html.eex:26 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 +#, elixir-autogen, elixir-format msgid "There are no transfers for this Token." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:93 +#, elixir-autogen, elixir-format +msgid "There are no verified contracts." +msgstr "" + #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 +#, elixir-autogen, elixir-format msgid "There is no coin history for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:29 +#, elixir-autogen, elixir-format msgid "There is no decompilded contracts for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 #: lib/block_scout_web/templates/chain/show.html.eex:9 +#, elixir-autogen, elixir-format msgid "There was a problem loading the chart." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:6 +#, elixir-autogen, elixir-format msgid "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +#, elixir-autogen, elixir-format msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " msgstr "" -#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:11 +#, elixir-autogen, elixir-format msgid "This block has not been processed yet." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:41 +#: lib/block_scout_web/templates/address_contract/index.html.eex:42 +#, elixir-autogen, elixir-format msgid "This contract has been partially verified via Sourcify." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:45 +#: lib/block_scout_web/templates/address_contract/index.html.eex:46 +#, elixir-autogen, elixir-format msgid "This contract has been verified via Sourcify." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +#, elixir-autogen, elixir-format msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:58 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "This transaction hasn't changed state." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:64 +#, elixir-autogen, elixir-format msgid "This transaction is pending confirmation." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:71 -#: lib/block_scout_web/templates/transaction/overview.html.eex:171 +#: lib/block_scout_web/templates/transaction/overview.html.eex:177 +#, elixir-autogen, elixir-format msgid "Timestamp" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 lib/block_scout_web/templates/address_transaction/index.html.eex:28 -#: lib/block_scout_web/templates/transaction/overview.html.eex:217 lib/block_scout_web/views/address_internal_transaction_view.ex:9 -#: lib/block_scout_web/views/address_token_transfer_view.ex:9 lib/block_scout_web/views/address_transaction_view.ex:9 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:28 +#: lib/block_scout_web/templates/transaction/overview.html.eex:221 +#: lib/block_scout_web/views/address_internal_transaction_view.ex:9 +#: lib/block_scout_web/views/address_token_transfer_view.ex:9 +#: lib/block_scout_web/views/address_transaction_view.ex:9 +#, elixir-autogen, elixir-format msgid "To" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 +#, elixir-autogen, elixir-format msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 +#, elixir-autogen, elixir-format msgid "To see accurate decoded input data, the contract must be verified." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:12 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 +#, elixir-autogen, elixir-format msgid "Toggle navigation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:73 +#: lib/block_scout_web/templates/address/overview.html.eex:55 +#, elixir-autogen, elixir-format msgid "Token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 lib/block_scout_web/views/transaction_view.ex:448 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 +#: lib/block_scout_web/views/transaction_view.ex:446 +#, elixir-autogen, elixir-format msgid "Token Burning" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 lib/block_scout_web/views/transaction_view.ex:449 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 +#: lib/block_scout_web/views/transaction_view.ex:447 +#, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 +#, elixir-autogen, elixir-format msgid "Token Details" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 lib/block_scout_web/views/tokens/overview_view.ex:40 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 +#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 +#: lib/block_scout_web/views/tokens/overview_view.ex:40 +#, elixir-autogen, elixir-format msgid "Token Holders" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 +#, elixir-autogen, elixir-format msgid "Token ID" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 lib/block_scout_web/views/transaction_view.ex:447 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 +#: lib/block_scout_web/views/transaction_view.ex:445 +#, elixir-autogen, elixir-format msgid "Token Minting" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 lib/block_scout_web/views/transaction_view.ex:450 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 +#: lib/block_scout_web/views/transaction_view.ex:448 +#, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:13 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 lib/block_scout_web/templates/transaction/_tabs.html.eex:4 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 lib/block_scout_web/views/address_view.ex:363 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:195 lib/block_scout_web/views/tokens/overview_view.ex:39 -#: lib/block_scout_web/views/transaction_view.ex:511 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 +#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 +#: lib/block_scout_web/views/address_view.ex:373 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:197 +#: lib/block_scout_web/views/tokens/overview_view.ex:39 +#: lib/block_scout_web/views/transaction_view.ex:509 +#, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:72 +#: lib/block_scout_web/templates/address/overview.html.eex:54 +#, elixir-autogen, elixir-format msgid "Token name and symbol." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 +#, elixir-autogen, elixir-format msgid "Token type" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:191 lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 lib/block_scout_web/templates/layout/_topnav.html.eex:67 -#: lib/block_scout_web/templates/tokens/index.html.eex:10 lib/block_scout_web/views/address_view.ex:360 +#: lib/block_scout_web/templates/address/overview.html.eex:175 +#: lib/block_scout_web/templates/address_token/overview.html.eex:58 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +#: lib/block_scout_web/templates/tokens/index.html.eex:10 +#: lib/block_scout_web/views/address_view.ex:370 +#, elixir-autogen, elixir-format msgid "Tokens" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:290 +#, elixir-autogen, elixir-format msgid "Tokens Burnt" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:306 +#, elixir-autogen, elixir-format msgid "Tokens Created" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:273 +#, elixir-autogen, elixir-format msgid "Tokens Minted" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:257 +#, elixir-autogen, elixir-format msgid "Tokens Transferred" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:13 +#, elixir-autogen, elixir-format msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 +#, elixir-autogen, elixir-format msgid "Topic" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 +#, elixir-autogen, elixir-format msgid "Topics" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Total" +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:169 +#, elixir-autogen, elixir-format msgid "Total Difficulty" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 +#, elixir-autogen, elixir-format msgid "Total Supply * Price" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:130 +#, elixir-autogen, elixir-format msgid "Total blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:168 +#, elixir-autogen, elixir-format msgid "Total difficulty of the chain until this block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:186 +#, elixir-autogen, elixir-format msgid "Total gas limit provided by all transactions in the block." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:69 +#, elixir-autogen, elixir-format msgid "Total supply" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:337 +#, elixir-autogen, elixir-format msgid "Total transaction fee." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:109 +#, elixir-autogen, elixir-format msgid "Total transactions" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:460 +#: lib/block_scout_web/views/transaction_view.ex:458 +#, elixir-autogen, elixir-format msgid "Transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 +#, elixir-autogen, elixir-format msgid "Transaction %{transaction} - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 +#, elixir-autogen, elixir-format msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:412 +#, elixir-autogen, elixir-format msgid "Transaction Burnt Fee" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:49 +#: lib/block_scout_web/templates/transaction/overview.html.eex:50 +#, elixir-autogen, elixir-format msgid "Transaction Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:338 +#, elixir-autogen, elixir-format msgid "Transaction Fee" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:74 +#: lib/block_scout_web/templates/transaction/overview.html.eex:80 +#, elixir-autogen, elixir-format msgid "Transaction Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 +#, elixir-autogen, elixir-format msgid "Transaction Inputs" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/common/_nav.html.eex:13 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 +#, elixir-autogen, elixir-format +msgid "Transaction Tags" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:362 +#, elixir-autogen, elixir-format msgid "Transaction Type" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:435 +#, elixir-autogen, elixir-format msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:361 +#, elixir-autogen, elixir-format msgid "Transaction type, introduced in EIP-2718." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:7 -#: lib/block_scout_web/templates/address/overview.html.eex:202 lib/block_scout_web/templates/address/overview.html.eex:208 -#: lib/block_scout_web/templates/address/overview.html.eex:216 lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/block/overview.html.eex:80 lib/block_scout_web/templates/block_transaction/index.html.eex:10 -#: lib/block_scout_web/templates/chain/show.html.eex:213 lib/block_scout_web/templates/layout/_topnav.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:362 +#: lib/block_scout_web/templates/address/overview.html.eex:186 +#: lib/block_scout_web/templates/address/overview.html.eex:192 +#: lib/block_scout_web/templates/address/overview.html.eex:200 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/block/overview.html.eex:80 +#: lib/block_scout_web/templates/block_transaction/index.html.eex:10 +#: lib/block_scout_web/templates/chain/show.html.eex:213 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 +#: lib/block_scout_web/views/address_view.ex:372 +#, elixir-autogen, elixir-format msgid "Transactions" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:119 +#: lib/block_scout_web/templates/address/overview.html.eex:101 +#, elixir-autogen, elixir-format msgid "Transactions and address of creation." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tile.html.eex:31 +#, elixir-autogen, elixir-format msgid "Transactions sent" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/address/overview.html.eex:235 lib/block_scout_web/templates/address/overview.html.eex:243 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 +#: lib/block_scout_web/templates/address/overview.html.eex:213 +#: lib/block_scout_web/templates/address/overview.html.eex:219 +#: lib/block_scout_web/templates/address/overview.html.eex:227 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 +#, elixir-autogen, elixir-format msgid "Transfers" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 +#, elixir-autogen, elixir-format msgid "Try it out" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Try to fetch constructor arguments automatically" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:27 +#, elixir-autogen, elixir-format msgid "Twitter" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/app.html.eex:49 +#, elixir-autogen, elixir-format msgid "Tx/day" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 +#, elixir-autogen, elixir-format +msgid "Txns" +msgstr "" + #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 +#, elixir-autogen, elixir-format msgid "Type" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:140 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 +#, elixir-autogen, elixir-format msgid "Type of the token standard" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 +#, elixir-autogen, elixir-format +msgid "UML diagram" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:461 +#, elixir-autogen, elixir-format msgid "UTF-8" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:75 +#: lib/block_scout_web/views/block_view.ex:77 +#, elixir-autogen, elixir-format msgid "Uncle Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:250 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:30 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 +#, elixir-autogen, elixir-format msgid "Uncles" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:341 +#: lib/block_scout_web/views/transaction_view.ex:339 +#, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 +#, elixir-autogen, elixir-format msgid "Unique Token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:73 +#: lib/block_scout_web/templates/transaction/overview.html.eex:79 +#, elixir-autogen, elixir-format msgid "Unique character string (TxID) assigned to every verified transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12 -msgid "Use the search box to find a hosted network, or select from the list of available networks below." +#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Update" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:391 +#, elixir-autogen, elixir-format msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:401 +#, elixir-autogen, elixir-format msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:224 +#, elixir-autogen, elixir-format msgid "User-defined tips sent to validator for transaction priority/inclusion." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:46 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#, elixir-autogen, elixir-format msgid "Validated" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:12 +#, elixir-autogen, elixir-format msgid "Validated Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 +#, elixir-autogen, elixir-format msgid "Validator Creation Date" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 +#, elixir-autogen, elixir-format msgid "Validator Data" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:51 -msgid "Validator Info" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:104 +#: lib/block_scout_web/templates/address/overview.html.eex:86 +#, elixir-autogen, elixir-format msgid "Validator Name" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:323 +#, elixir-autogen, elixir-format msgid "Value" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:322 +#, elixir-autogen, elixir-format msgid "Value sent in the native token (and USD) if applicable." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:82 +#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Verifed contracts, %{subnetwork}, %{coin}" +msgstr "" + +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 +#, elixir-autogen, elixir-format +msgid "Verified" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Verified Contracts" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:83 +#, elixir-autogen, elixir-format msgid "Verified at" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:160 -#: lib/block_scout_web/templates/address_contract/index.html.eex:166 lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:203 lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:68 +#, elixir-autogen, elixir-format +msgid "Verified contracts" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Verified contracts - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#: lib/block_scout_web/templates/address_contract/index.html.eex:30 +#: lib/block_scout_web/templates/address_contract/index.html.eex:192 +#: lib/block_scout_web/templates/address_contract/index.html.eex:223 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 +#, elixir-autogen, elixir-format msgid "Verify & Publish" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:141 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 +#, elixir-autogen, elixir-format msgid "Verify & publish" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#, elixir-autogen, elixir-format msgid "Verify the contract " msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_footer.html.eex:91 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 +#, elixir-autogen, elixir-format msgid "Version" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 +#, elixir-autogen, elixir-format msgid "Via Sourcify: Sources and metadata JSON file" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 +#, elixir-autogen, elixir-format msgid "Via Standard Input JSON" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 +#, elixir-autogen, elixir-format msgid "Via flattened source code" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 +#, elixir-autogen, elixir-format +msgid "Via multi-part files" +msgstr "" + #: lib/block_scout_web/templates/chain/show.html.eex:152 +#, elixir-autogen, elixir-format msgid "View All Blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:212 +#, elixir-autogen, elixir-format msgid "View All Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 +#, elixir-autogen, elixir-format msgid "View Contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:67 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 +#, elixir-autogen, elixir-format msgid "View Less Transfers" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:66 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 +#, elixir-autogen, elixir-format msgid "View More Transfers" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:39 +#, elixir-autogen, elixir-format msgid "View next block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:23 +#, elixir-autogen, elixir-format msgid "View previous block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:9 +#, elixir-autogen, elixir-format msgid "View the account balance, transactions, and other data for %{address} on the %{network}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:10 +#, elixir-autogen, elixir-format msgid "View the transactions, token transfers, and uncles for block number %{block_number}" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 +#, elixir-autogen, elixir-format +msgid "View the verified contracts on %{subnetwork}" +msgstr "" + #: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 +#, elixir-autogen, elixir-format msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:39 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 +#: lib/block_scout_web/views/verified_contracts_view.ex:10 +#, elixir-autogen, elixir-format +msgid "Vyper" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 +#, elixir-autogen, elixir-format msgid "Vyper contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:145 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:136 +#, elixir-autogen, elixir-format msgid "WEI" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 +#, elixir-autogen, elixir-format msgid "Waiting for transaction's confirmation..." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:138 +#, elixir-autogen, elixir-format msgid "Wallet addresses" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 +#, elixir-autogen, elixir-format msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/common/_nav.html.eex:7 +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Watch list" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 +#, elixir-autogen, elixir-format +msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" +msgstr "" + #: lib/block_scout_web/views/wei_helpers.ex:76 +#, elixir-autogen, elixir-format msgid "Wei" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:109 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format msgid "Write" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:95 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 lib/block_scout_web/views/address_view.ex:368 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 +#: lib/block_scout_web/views/address_view.ex:378 +#, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:102 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 lib/block_scout_web/views/address_view.ex:369 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 +#: lib/block_scout_web/views/address_view.ex:379 +#, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:47 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 +#, elixir-autogen, elixir-format msgid "Yes" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:129 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "You can create 3 API keys per account." +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "You can create up to 15 Custom ABIs per account." +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:11 +#, elixir-autogen, elixir-format +msgid "You can request a public category tag which is displayed to all Blockscout users. Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag. Clicking a tag opens a page with related information and helps provide context and data organization. Requests are sent to a moderator for review and approval. This process can take several days." +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "You don't have address tags yet" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 +#, elixir-autogen, elixir-format +msgid "You don't have addresses on you watchlist yet" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "You don't have transaction tags yet" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Your name" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Your name*" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:111 +#, elixir-autogen, elixir-format msgid "at" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format msgid "balance of the address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:411 +#, elixir-autogen, elixir-format msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "button" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:228 +#: lib/block_scout_web/templates/transaction/overview.html.eex:230 +#, elixir-autogen, elixir-format msgid "created" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +#, elixir-autogen, elixir-format msgid "custom RPC" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:149 +#, elixir-autogen, elixir-format +msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." +msgstr "" + #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format msgid "elements are displayed" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:38 +#, elixir-autogen, elixir-format msgid "fallback" msgstr "" -#, elixir-format #: lib/block_scout_web/views/address_contract_view.ex:24 +#, elixir-autogen, elixir-format msgid "false" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#, elixir-autogen, elixir-format msgid "here" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +#, elixir-autogen, elixir-format msgid "here." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/invalid.html.eex:8 -msgid "is not a valid transaction hash" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 +#, elixir-autogen, elixir-format msgid "method Response" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format msgid "of" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 +#, elixir-autogen, elixir-format msgid "page" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:40 +#, elixir-autogen, elixir-format msgid "receive" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 +#, elixir-autogen, elixir-format msgid "required" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 +#, elixir-autogen, elixir-format msgid "string" msgstr "" -#, elixir-format #: lib/block_scout_web/views/address_contract_view.ex:23 +#, elixir-autogen, elixir-format msgid "true" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:9 -msgid "Error in internal transactions" +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 +#, elixir-autogen, elixir-format +msgid "truffle flattener" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -msgid "Average" +#: lib/block_scout_web/templates/transaction/not_found.html.eex:8 +#, elixir-autogen, elixir-format +msgid "1. If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/show.html.eex:69 -msgid "Daily Transactions" +#: lib/block_scout_web/templates/transaction/not_found.html.eex:9 +#, elixir-autogen, elixir-format +msgid "2. It could still be in the TX Pool of a different node, waiting to be broadcasted." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -msgid "Fast" +#: lib/block_scout_web/templates/transaction/not_found.html.eex:11 +#, elixir-autogen, elixir-format +msgid "4. If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 -msgid "Gas tracker" +#: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "An unexpected error has occurred. Try reloading the page, or come back soon and try again." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -msgid "Slow" +#: lib/block_scout_web/templates/error422/index.html.eex:9 +#: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 +#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 +#: lib/block_scout_web/templates/transaction/not_found.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Back to home" +msgstr "" + +#: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Internal server error" +msgstr "" + +#: lib/block_scout_web/templates/error422/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Request cannot be processed" +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Sorry, we are unable to locate this transaction hash" +msgstr "" + +#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for." +msgstr "" + +#: lib/block_scout_web/templates/error422/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:10 +#, elixir-autogen, elixir-format +msgid "3. During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 5f460bb96aa8..55fd2103ed00 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1,2679 +1,3501 @@ -#, elixir-format #: lib/block_scout_web/views/address_token_balance_view.ex:10 +#, elixir-autogen, elixir-format msgid "%{count} token" msgid_plural "%{count} tokens" msgstr[0] "" msgstr[1] "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:29 +#, elixir-autogen, elixir-format msgid "%{count} transaction" msgid_plural "%{count} transactions" msgstr[0] "" msgstr[1] "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 +#, elixir-autogen, elixir-format msgid " - minimal bytecode implementation that delegates all calls to a known address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +#, elixir-autogen, elixir-format msgid " is recommended." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:3 +#, elixir-autogen, elixir-format msgid "%{address} - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:12 +#, elixir-autogen, elixir-format msgid "%{block_type} Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:55 +#, elixir-autogen, elixir-format msgid "%{block_type} Height" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:7 +#, elixir-autogen, elixir-format msgid "%{block_type}s" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:85 +#, elixir-autogen, elixir-format msgid "%{count} Transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/chain/_block.html.eex:11 +#, elixir-autogen, elixir-format msgid "%{count} Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:2 +#, elixir-autogen, elixir-format msgid "%{subnetwork} %{network} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_default_title.html.eex:2 +#, elixir-autogen, elixir-format msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:349 +#: lib/block_scout_web/views/transaction_view.ex:347 +#, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 +#, elixir-autogen, elixir-format msgid "(query)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:223 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 +#, elixir-autogen, elixir-format +msgid ") may be added for each contract. Click the Add Library button to add an additional one." +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:236 +#, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:195 +#, elixir-autogen, elixir-format msgid "64-bit hash of value verifying proof-of-work (note: null for POA chains)." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:97 +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 +#, elixir-autogen, elixir-format msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 +#, elixir-autogen, elixir-format +msgid "A library name called in the .sol file. Multiple libraries (up to " +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 +#, elixir-autogen, elixir-format msgid "A string with the name of the action to be invoked." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 +#, elixir-autogen, elixir-format msgid "A string with the name of the module to be invoked." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "ABI" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 +#, elixir-autogen, elixir-format msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 +#, elixir-autogen, elixir-format msgid "API Documentation" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 +#, elixir-autogen, elixir-format msgid "API endpoints for the %{subnetwork}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 +#, elixir-autogen, elixir-format msgid "API for the %{subnetwork} - BlockScout" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:13 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:14 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:29 +#, elixir-autogen, elixir-format +msgid "API key" +msgstr "" + +#: lib/block_scout_web/templates/account/api_key/index.html.eex:7 +#: lib/block_scout_web/templates/account/common/_nav.html.eex:16 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 +#, elixir-autogen, elixir-format +msgid "API keys" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:100 +#, elixir-autogen, elixir-format msgid "APIs" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 +#, elixir-autogen, elixir-format msgid "Action" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 +#, elixir-autogen, elixir-format +msgid "Actions" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:425 +#, elixir-autogen, elixir-format msgid "Actual gas amount used by the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_footer.html.eex:44 +#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 +#: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:11 +#, elixir-autogen, elixir-format msgid "Add" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/index.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Add API key" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 +#, elixir-autogen, elixir-format +msgid "Add Contract Libraries" +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Add Custom ABI" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 +#, elixir-autogen, elixir-format +msgid "Add Library" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 +#, elixir-autogen, elixir-format +msgid "Add address" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 +#, elixir-autogen, elixir-format +msgid "Add address tag" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Add address to the Watch list" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 +#, elixir-autogen, elixir-format +msgid "Add transaction tag" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 lib/block_scout_web/views/address_view.ex:104 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:29 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:54 +#: lib/block_scout_web/views/address_view.ex:107 +#, elixir-autogen, elixir-format msgid "Address" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:213 +#: lib/block_scout_web/templates/transaction/overview.html.eex:217 +#, elixir-autogen, elixir-format msgid "Address (external or contract) receiving the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:193 +#: lib/block_scout_web/templates/transaction/overview.html.eex:199 +#, elixir-autogen, elixir-format msgid "Address (external or contract) sending the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:165 -msgid "Address balance in xDAI (doesn't include ERC20, ERC721, ERC1155 tokens)." +#: lib/block_scout_web/templates/account/common/_nav.html.eex:10 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Address Tags" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:149 +#, elixir-autogen, elixir-format +msgid "Address balance in" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:50 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 +#, elixir-autogen, elixir-format msgid "Address of the token contract" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Address used in token mintings and burnings." +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Address*" +msgstr "" + #: lib/block_scout_web/templates/address/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Addresses" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 lib/block_scout_web/templates/layout/_topnav.html.eex:71 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 lib/block_scout_web/views/address_token_transfer_view.ex:11 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:82 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 +#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 +#: lib/block_scout_web/views/address_token_transfer_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11 +#: lib/block_scout_web/views/verified_contracts_view.ex:11 +#, elixir-autogen, elixir-format msgid "All" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 +#, elixir-autogen, elixir-format msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:190 +#: lib/block_scout_web/templates/address/overview.html.eex:174 +#, elixir-autogen, elixir-format msgid "All tokens in the account and total value." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:411 +#, elixir-autogen, elixir-format msgid "Amount of" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:236 +#, elixir-autogen, elixir-format msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +#, elixir-autogen, elixir-format msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:117 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#, elixir-autogen, elixir-format msgid "Apps" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Average" +msgstr "" + #: lib/block_scout_web/templates/chain/show.html.eex:100 +#, elixir-autogen, elixir-format msgid "Average block time" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:30 -msgid "Back Home" +#: lib/block_scout_web/templates/account/api_key/form.html.eex:25 +#, elixir-autogen, elixir-format +msgid "Back to API keys (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Back to Address Tags (Cancel)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:166 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Back to Custom ABI (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Back to Transaction Tags (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 +#, elixir-autogen, elixir-format +msgid "Back to Watch list (Cancel)" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 +#: lib/block_scout_web/templates/address/overview.html.eex:150 #: lib/block_scout_web/templates/address_token/overview.html.eex:51 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:57 +#, elixir-autogen, elixir-format msgid "Balance" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_state/index.html.eex:35 +#, elixir-autogen, elixir-format +msgid "Balance after" +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/index.html.eex:32 +#, elixir-autogen, elixir-format +msgid "Balance before" +msgstr "" + #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 +#, elixir-autogen, elixir-format msgid "Balances" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:207 +#, elixir-autogen, elixir-format msgid "Base Fee per Gas" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Base URL:" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:446 +#, elixir-autogen, elixir-format msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 -#: lib/block_scout_web/templates/block/overview.html.eex:29 lib/block_scout_web/templates/transaction/overview.html.eex:152 +#: lib/block_scout_web/templates/block/overview.html.eex:29 +#: lib/block_scout_web/templates/transaction/overview.html.eex:158 +#, elixir-autogen, elixir-format msgid "Block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 +#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 +#, elixir-autogen, elixir-format msgid "Block #%{number}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:3 +#, elixir-autogen, elixir-format msgid "Block %{block_number} - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block_transaction/404.html.eex:7 +#, elixir-autogen, elixir-format msgid "Block Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:53 +#, elixir-autogen, elixir-format msgid "Block Height" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/app.html.eex:43 +#, elixir-autogen, elixir-format msgid "Block Mined, awaiting import..." msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:33 +#: lib/block_scout_web/views/transaction_view.ex:34 +#, elixir-autogen, elixir-format msgid "Block Pending" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:158 +#, elixir-autogen, elixir-format msgid "Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks)." msgstr "" -#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:15 +#, elixir-autogen, elixir-format msgid "Block not found, please try again later." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:151 +#: lib/block_scout_web/templates/transaction/overview.html.eex:157 +#, elixir-autogen, elixir-format msgid "Block number containing the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:273 +#: lib/block_scout_web/templates/address/overview.html.eex:257 +#, elixir-autogen, elixir-format msgid "Block number in which the address was updated." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:4 +#, elixir-autogen, elixir-format msgid "BlockScout provides analytics data, API, and Smart Contract tools for the %{subnetwork}" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:29 +#, elixir-autogen, elixir-format +msgid "Blockchain" +msgstr "" + #: lib/block_scout_web/templates/chain/show.html.eex:153 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:23 lib/block_scout_web/templates/layout/_topnav.html.eex:27 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:38 +#, elixir-autogen, elixir-format msgid "Blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/app.html.eex:42 +#, elixir-autogen, elixir-format msgid "Blocks Indexed" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:48 -#: lib/block_scout_web/templates/address/overview.html.eex:291 lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:371 +#: lib/block_scout_web/templates/address/overview.html.eex:275 +#: lib/block_scout_web/templates/address_validation/index.html.eex:11 +#: lib/block_scout_web/views/address_view.ex:381 +#, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Blocks With Internal Transactions Indexed" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:22 +#, elixir-autogen, elixir-format msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Burn address" +msgstr "" + #: lib/block_scout_web/templates/block/_tile.html.eex:64 #: lib/block_scout_web/templates/block/overview.html.eex:216 +#, elixir-autogen, elixir-format msgid "Burnt Fees" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:65 +#, elixir-autogen, elixir-format msgid "CRC Worth" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2 +#, elixir-autogen, elixir-format msgid "CSV" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 #: lib/block_scout_web/views/internal_transaction_view.ex:21 +#, elixir-autogen, elixir-format msgid "Call" msgstr "" -#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:22 +#, elixir-autogen, elixir-format msgid "Call Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:105 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:145 +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:62 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 +#, elixir-autogen, elixir-format msgid "Cancel" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 -msgid "Change Network" +#: lib/block_scout_web/templates/transaction_state/index.html.eex:38 +#, elixir-autogen, elixir-format +msgid "Change" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:41 +#, elixir-autogen, elixir-format msgid "Chat (#blockscout)" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:63 +#: lib/block_scout_web/views/block_view.ex:65 +#, elixir-autogen, elixir-format msgid "Chore Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 +#, elixir-autogen, elixir-format msgid "Clear" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 +#, elixir-autogen, elixir-format msgid "Close" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:58 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:364 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 +#: lib/block_scout_web/views/address_view.ex:374 +#, elixir-autogen, elixir-format msgid "Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:370 +#: lib/block_scout_web/views/address_view.ex:380 +#, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format msgid "Collapse" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Company name" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:32 +#, elixir-autogen, elixir-format +msgid "Company website" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 +#, elixir-autogen, elixir-format msgid "Compiler" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:65 +#: lib/block_scout_web/templates/address_contract/index.html.eex:137 +#, elixir-autogen, elixir-format +msgid "Compiler Settings" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:66 +#, elixir-autogen, elixir-format msgid "Compiler version" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:342 +#: lib/block_scout_web/views/transaction_view.ex:340 +#, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:118 +#: lib/block_scout_web/templates/transaction/overview.html.eex:124 +#, elixir-autogen, elixir-format msgid "Confirmed by " msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:184 +#: lib/block_scout_web/templates/transaction/overview.html.eex:190 +#, elixir-autogen, elixir-format msgid "Confirmed within" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 lib/block_scout_web/templates/tokens/holder/index.html.eex:15 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 +#, elixir-autogen, elixir-format msgid "Connection Lost" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/block/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer internal transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 lib/block_scout_web/templates/transaction/index.html.eex:22 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 +#: lib/block_scout_web/templates/transaction/index.html.eex:22 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:10 +#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer validations" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:90 +#: lib/block_scout_web/templates/address_contract/index.html.eex:91 +#, elixir-autogen, elixir-format msgid "Constructor Arguments" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 -#: lib/block_scout_web/templates/transaction/overview.html.eex:223 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 +#, elixir-autogen, elixir-format +msgid "Constructor args" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 +#: lib/block_scout_web/templates/transaction/overview.html.eex:227 +#, elixir-autogen, elixir-format msgid "Contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:122 +#: lib/block_scout_web/templates/address_contract/index.html.eex:152 +#, elixir-autogen, elixir-format msgid "Contract ABI" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 lib/block_scout_web/views/address_view.ex:102 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 +#: lib/block_scout_web/views/address_view.ex:105 +#, elixir-autogen, elixir-format msgid "Contract Address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:42 lib/block_scout_web/views/address_view.ex:76 +#: lib/block_scout_web/views/address_view.ex:45 +#: lib/block_scout_web/views/address_view.ex:79 +#, elixir-autogen, elixir-format msgid "Contract Address Pending" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:457 +#: lib/block_scout_web/views/transaction_view.ex:455 +#, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:454 +#: lib/block_scout_web/views/transaction_view.ex:452 +#, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:138 -#: lib/block_scout_web/templates/address_contract/index.html.eex:153 +#: lib/block_scout_web/templates/address_contract/index.html.eex:169 +#: lib/block_scout_web/templates/address_contract/index.html.eex:184 +#, elixir-autogen, elixir-format msgid "Contract Creation Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 +#, elixir-autogen, elixir-format msgid "Contract Libraries" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:93 +#: lib/block_scout_web/templates/address/overview.html.eex:75 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 +#, elixir-autogen, elixir-format msgid "Contract Name" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:25 +#: lib/block_scout_web/templates/address_contract/index.html.eex:26 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 +#, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:57 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Contract name or address" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:58 +#, elixir-autogen, elixir-format msgid "Contract name:" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:100 +#: lib/block_scout_web/templates/address_contract/index.html.eex:101 +#, elixir-autogen, elixir-format msgid "Contract source code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:144 +#: lib/block_scout_web/templates/address/overview.html.eex:120 +#, elixir-autogen, elixir-format +msgid "Contract was precompiled and created at genesis or contract creation transaction is missing" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Contracts" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:175 +#, elixir-autogen, elixir-format msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:40 +#, elixir-autogen, elixir-format msgid "Contribute" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:124 +#: lib/block_scout_web/templates/address_contract/index.html.eex:154 +#, elixir-autogen, elixir-format msgid "Copy ABI" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:38 lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:105 lib/block_scout_web/templates/tokens/overview/_details.html.eex:42 +#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 +#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Copy API key" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 +#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 +#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 +#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 +#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 +#: lib/block_scout_web/templates/address/overview.html.eex:38 +#: lib/block_scout_web/templates/address/overview.html.eex:39 +#: lib/block_scout_web/templates/block/overview.html.eex:104 +#: lib/block_scout_web/templates/block/overview.html.eex:105 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 +#, elixir-autogen, elixir-format msgid "Copy Address" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:140 -#: lib/block_scout_web/templates/address_contract/index.html.eex:156 +#: lib/block_scout_web/templates/address_contract/index.html.eex:139 +#, elixir-autogen, elixir-format +msgid "Copy Compiler Settings" +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Copy Contract Address" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:171 +#: lib/block_scout_web/templates/address_contract/index.html.eex:187 +#, elixir-autogen, elixir-format msgid "Copy Contract Creation Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:19 +#, elixir-autogen, elixir-format msgid "Copy Decompiled Contract Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:183 -#: lib/block_scout_web/templates/address_contract/index.html.eex:193 +#: lib/block_scout_web/templates/address_contract/index.html.eex:208 +#: lib/block_scout_web/templates/address_contract/index.html.eex:218 +#, elixir-autogen, elixir-format msgid "Copy Deployed ByteCode" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:14 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:15 lib/block_scout_web/templates/transaction/overview.html.eex:203 -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 +#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 +#: lib/block_scout_web/templates/transaction/overview.html.eex:207 +#: lib/block_scout_web/templates/transaction/overview.html.eex:208 +#, elixir-autogen, elixir-format msgid "Copy From Address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:130 +#, elixir-autogen, elixir-format msgid "Copy Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 +#, elixir-autogen, elixir-format msgid "Copy Metadata" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:150 +#, elixir-autogen, elixir-format msgid "Copy Parent Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15 +#, elixir-autogen, elixir-format msgid "Copy Raw Trace" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:102 -#: lib/block_scout_web/templates/address_contract/index.html.eex:113 +#: lib/block_scout_web/templates/address_contract/index.html.eex:115 +#: lib/block_scout_web/templates/address_contract/index.html.eex:127 +#, elixir-autogen, elixir-format msgid "Copy Source Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:31 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:32 lib/block_scout_web/templates/transaction/overview.html.eex:232 -#: lib/block_scout_web/templates/transaction/overview.html.eex:233 lib/block_scout_web/templates/transaction/overview.html.eex:242 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 +#: lib/block_scout_web/templates/transaction/overview.html.eex:234 +#: lib/block_scout_web/templates/transaction/overview.html.eex:235 +#: lib/block_scout_web/templates/transaction/overview.html.eex:242 #: lib/block_scout_web/templates/transaction/overview.html.eex:243 +#, elixir-autogen, elixir-format msgid "Copy To Address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 +#, elixir-autogen, elixir-format msgid "Copy Token ID" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:81 +#: lib/block_scout_web/templates/transaction/overview.html.eex:87 +#, elixir-autogen, elixir-format msgid "Copy Transaction Hash" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:82 +#: lib/block_scout_web/templates/transaction/overview.html.eex:88 +#, elixir-autogen, elixir-format msgid "Copy Txn Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:472 +#, elixir-autogen, elixir-format msgid "Copy Txn Hex Input" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:478 +#, elixir-autogen, elixir-format msgid "Copy Txn UTF-8 Input" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 lib/block_scout_web/templates/transaction/overview.html.eex:471 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 +#: lib/block_scout_web/templates/transaction/overview.html.eex:471 #: lib/block_scout_web/templates/transaction/overview.html.eex:477 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:14 +#, elixir-autogen, elixir-format msgid "Copy Value" msgstr "" -#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:25 +#, elixir-autogen, elixir-format msgid "Create" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "Create a Custom ABI to interact with contracts." +msgstr "" + +#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "Create an API key to use with your RPC и EthRPC API requests." +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:26 +#, elixir-autogen, elixir-format msgid "Create2" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:120 +#: lib/block_scout_web/templates/address/overview.html.eex:102 +#, elixir-autogen, elixir-format msgid "Creator" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 +#, elixir-autogen, elixir-format msgid "Curl" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:91 +#: lib/block_scout_web/templates/transaction/overview.html.eex:97 +#, elixir-autogen, elixir-format msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Custom" +msgstr "" + +#: lib/block_scout_web/templates/account/common/_nav.html.eex:19 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Custom ABI" +msgstr "" + +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 +#, elixir-autogen, elixir-format +msgid "Custom ABI from account" +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:69 +#, elixir-autogen, elixir-format +msgid "Daily Transactions" +msgstr "" + #: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 +#, elixir-autogen, elixir-format msgid "Data" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:70 +#, elixir-autogen, elixir-format msgid "Date & time at which block was produced." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:170 +#: lib/block_scout_web/templates/transaction/overview.html.eex:176 +#, elixir-autogen, elixir-format msgid "Date & time of transaction inclusion, including length of time for confirmation." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 +#, elixir-autogen, elixir-format msgid "Decimals" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:34 lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57 lib/block_scout_web/templates/transaction_log/_logs.html.eex:73 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:34 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73 +#, elixir-autogen, elixir-format msgid "Decoded" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/address_view.ex:365 +#: lib/block_scout_web/views/address_view.ex:375 +#, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:75 +#, elixir-autogen, elixir-format msgid "Decompiled code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "Decompiled contract code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:10 +#, elixir-autogen, elixir-format msgid "Decompiler version" msgstr "" -#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:23 +#, elixir-autogen, elixir-format msgid "Delegate Call" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 -#: lib/block_scout_web/templates/address_contract/index.html.eex:189 +#: lib/block_scout_web/templates/address_contract/index.html.eex:206 +#: lib/block_scout_web/templates/address_contract/index.html.eex:214 +#, elixir-autogen, elixir-format msgid "Deployed ByteCode" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 +#, elixir-autogen, elixir-format msgid "Description" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:56 +#, elixir-autogen, elixir-format +msgid "Description*" +msgstr "" + #: lib/block_scout_web/templates/address/overview.html.eex:30 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 +#, elixir-autogen, elixir-format msgid "Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:159 +#, elixir-autogen, elixir-format msgid "Difficulty" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:145 +#: lib/block_scout_web/templates/address_contract/index.html.eex:176 +#, elixir-autogen, elixir-format msgid "Displaying the init data provided of the creating transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/csv_export/index.html.eex:25 +#: lib/block_scout_web/templates/csv_export/index.html.eex:24 +#, elixir-autogen, elixir-format msgid "Download" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 +#, elixir-autogen, elixir-format +msgid "Drop all Solidity contract source files into the drop zone." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 +#, elixir-autogen, elixir-format +msgid "Drop all Solidity or Yul contract source files into the drop zone." +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 +#, elixir-autogen, elixir-format msgid "Drop sources and metadata JSON file or click here" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 +#, elixir-autogen, elixir-format +msgid "Drop sources or click here" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 +#, elixir-autogen, elixir-format msgid "Drop the standard input JSON file or click here" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:22 -msgid "During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:27 +#, elixir-autogen, elixir-format +msgid "E-mail*" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 +#, elixir-autogen, elixir-format msgid "EIP-1167" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:214 +#: lib/block_scout_web/views/transaction_view.ex:212 +#, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:212 +#: lib/block_scout_web/views/transaction_view.ex:210 +#, elixir-autogen, elixir-format msgid "ERC-20 " msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:213 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 +#, elixir-autogen, elixir-format +msgid "ERC-20 tokens (beta)" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:211 +#, elixir-autogen, elixir-format msgid "ERC-721 " msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_token/overview.html.eex:1 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:104 lib/block_scout_web/templates/smart_contract/_functions.html.eex:104 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:146 -msgid "ETH" +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 +#, elixir-autogen, elixir-format +msgid "ERC-721, ERC-1155 tokens (NFT) (beta)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +#, elixir-autogen, elixir-format msgid "ETH RPC API Documentation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 +#: lib/block_scout_web/templates/address_contract/index.html.eex:77 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 +#, elixir-autogen, elixir-format msgid "EVM Version" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 +#, elixir-autogen, elixir-format +msgid "EVM version details" +msgstr "" + #: lib/block_scout_web/views/block_transaction_view.ex:7 +#, elixir-autogen, elixir-format msgid "Easy Cowboy! This block does not exist yet!" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/row.html.eex:16 +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 +#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 +#, elixir-autogen, elixir-format +msgid "Edit" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Edit Watch list address" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 +#, elixir-autogen, elixir-format +msgid "Email notifications" +msgstr "" + #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 +#, elixir-autogen, elixir-format msgid "Emission Contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:71 +#: lib/block_scout_web/views/block_view.ex:73 +#, elixir-autogen, elixir-format msgid "Emission Reward" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:68 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 +#, elixir-autogen, elixir-format msgid "Enter the Solidity Contract Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 +#, elixir-autogen, elixir-format msgid "Enter the Vyper Contract Code" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 +#, elixir-autogen, elixir-format msgid "Error" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Error in internal transactions" +msgstr "" + #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 +#, elixir-autogen, elixir-format msgid "Error rendering value" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 +#, elixir-autogen, elixir-format msgid "Error trying to fetch balances." msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:353 +#: lib/block_scout_web/views/transaction_view.ex:351 +#, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:351 +#: lib/block_scout_web/views/transaction_view.ex:349 +#, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:137 +#: lib/block_scout_web/templates/address/overview.html.eex:120 +#, elixir-autogen, elixir-format msgid "Error: Could not determine contract creator." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:103 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#, elixir-autogen, elixir-format msgid "Eth RPC" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/_current_coin_balance.html.eex:11 -#: lib/block_scout_web/templates/address/index.html.eex:5 lib/block_scout_web/templates/address/overview.html.eex:179 -#: lib/block_scout_web/templates/block/overview.html.eex:215 lib/block_scout_web/templates/internal_transaction/_tile.html.eex:24 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:76 lib/block_scout_web/templates/layout/app.html.eex:48 -#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 lib/block_scout_web/templates/transaction/_tile.html.eex:43 -#: lib/block_scout_web/templates/transaction/overview.html.eex:411 lib/block_scout_web/views/wei_helpers.ex:78 -msgid "Ether" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 +#, elixir-autogen, elixir-format msgid "Example Value" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 +#, elixir-autogen, elixir-format msgid "Execute" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format msgid "Expand" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/csv_export/index.html.eex:10 +#, elixir-autogen, elixir-format msgid "Export Data" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:224 +#: lib/block_scout_web/templates/address_contract/index.html.eex:243 +#, elixir-autogen, elixir-format msgid "External libraries" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 +#, elixir-autogen, elixir-format msgid "Failed to decode input data." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:37 +#, elixir-autogen, elixir-format msgid "Failed to decode log data." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:24 -msgid "Favorites" +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Fast" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:263 +#: lib/block_scout_web/templates/address/overview.html.eex:247 +#, elixir-autogen, elixir-format msgid "Fetching gas used..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:111 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 +#, elixir-autogen, elixir-format msgid "Fetching holders..." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 +#, elixir-autogen, elixir-format msgid "Fetching tokens..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:210 -#: lib/block_scout_web/templates/address/overview.html.eex:218 +#: lib/block_scout_web/templates/address/overview.html.eex:194 +#: lib/block_scout_web/templates/address/overview.html.eex:202 +#, elixir-autogen, elixir-format msgid "Fetching transactions..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:237 -#: lib/block_scout_web/templates/address/overview.html.eex:245 lib/block_scout_web/templates/tokens/overview/_details.html.eex:122 +#: lib/block_scout_web/templates/address/overview.html.eex:221 +#: lib/block_scout_web/templates/address/overview.html.eex:229 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 +#, elixir-autogen, elixir-format msgid "Fetching transfers..." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Filter by compiler:" +msgstr "" + #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 +#, elixir-autogen, elixir-format msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:33 +#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "For contract" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 +#, elixir-autogen, elixir-format msgid "Forked Blocks (Reorgs)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_footer.html.eex:42 +#: lib/block_scout_web/templates/layout/_footer.html.eex:43 +#, elixir-autogen, elixir-format msgid "Forum" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_token_transfer_view.ex:10 lib/block_scout_web/views/address_transaction_view.ex:10 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:34 +#: lib/block_scout_web/templates/transaction/overview.html.eex:200 +#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 +#: lib/block_scout_web/views/address_token_transfer_view.ex:10 +#: lib/block_scout_web/views/address_transaction_view.ex:10 +#, elixir-autogen, elixir-format msgid "From" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 +#, elixir-autogen, elixir-format msgid "GET" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:67 -#: lib/block_scout_web/templates/block/overview.html.eex:187 lib/block_scout_web/templates/transaction/overview.html.eex:373 +#: lib/block_scout_web/templates/block/overview.html.eex:187 +#: lib/block_scout_web/templates/transaction/overview.html.eex:373 +#, elixir-autogen, elixir-format msgid "Gas Limit" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:353 +#, elixir-autogen, elixir-format msgid "Gas Price" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:256 -#: lib/block_scout_web/templates/block/_tile.html.eex:73 lib/block_scout_web/templates/block/overview.html.eex:178 +#: lib/block_scout_web/templates/address/overview.html.eex:240 +#: lib/block_scout_web/templates/block/_tile.html.eex:73 +#: lib/block_scout_web/templates/block/overview.html.eex:178 +#, elixir-autogen, elixir-format msgid "Gas Used" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#, elixir-autogen, elixir-format msgid "Gas Used by Transaction" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:255 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Gas tracker" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:239 +#, elixir-autogen, elixir-format msgid "Gas used by the address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:60 +#, elixir-autogen, elixir-format msgid "Genesis Block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:24 +#, elixir-autogen, elixir-format msgid "Github" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 +#, elixir-autogen, elixir-format msgid "Go to" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:93 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:104 +#, elixir-autogen, elixir-format msgid "GraphQL" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 -#: lib/block_scout_web/views/block_view.ex:21 lib/block_scout_web/views/wei_helpers.ex:77 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 +#: lib/block_scout_web/views/block_view.ex:22 +#: lib/block_scout_web/views/wei_helpers.ex:77 +#, elixir-autogen, elixir-format msgid "Gwei" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:123 +#, elixir-autogen, elixir-format msgid "Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:454 #: lib/block_scout_web/templates/transaction/overview.html.eex:458 +#, elixir-autogen, elixir-format msgid "Hex (Default)" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 +#, elixir-autogen, elixir-format msgid "Holders" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +#, elixir-autogen, elixir-format msgid "However, in general, the" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 +#, elixir-autogen, elixir-format msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:86 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 +#, elixir-autogen, elixir-format msgid "IN" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:26 -msgid "If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 +#, elixir-autogen, elixir-format +msgid "If you enabled optimization during compilation, select yes." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:12 -msgid "If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:149 +#: lib/block_scout_web/templates/address/overview.html.eex:133 +#, elixir-autogen, elixir-format msgid "Implementation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:148 +#: lib/block_scout_web/templates/address/overview.html.eex:132 +#, elixir-autogen, elixir-format msgid "Implementation address of the proxy contract." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Include nightly builds" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 +#, elixir-autogen, elixir-format +msgid "Incoming" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:438 +#, elixir-autogen, elixir-format msgid "Index position of Transaction in the block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:249 +#, elixir-autogen, elixir-format msgid "Index position(s) of referenced stale blocks." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 +#, elixir-autogen, elixir-format msgid "Indexed?" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:44 -msgid "Indexing Tokens" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 +#, elixir-autogen, elixir-format msgid "Input" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:215 +#: lib/block_scout_web/templates/transaction/overview.html.eex:219 +#, elixir-autogen, elixir-format msgid "Interacted With (To)" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 +#, elixir-autogen, elixir-format msgid "Internal Transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:28 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 lib/block_scout_web/views/address_view.ex:361 -#: lib/block_scout_web/views/transaction_view.ex:512 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 +#: lib/block_scout_web/views/address_view.ex:371 +#: lib/block_scout_web/views/transaction_view.ex:510 +#, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/invalid.html.eex:6 -msgid "Invalid Transaction Hash" +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 +#: lib/block_scout_web/views/tokens/overview_view.ex:42 +#, elixir-autogen, elixir-format +msgid "Inventory" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:15 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 lib/block_scout_web/views/tokens/overview_view.ex:42 -msgid "Inventory" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Is Yul contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:16 -msgid "It could still be in the TX Pool of a different node, waiting to be broadcasted." +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 +#, elixir-autogen, elixir-format +msgid "Last 24h" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:274 +#: lib/block_scout_web/templates/address/overview.html.eex:258 +#, elixir-autogen, elixir-format msgid "Last Balance Update" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:45 -msgid "Less than" +#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Learn more" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:3 -msgid "Library Address" +#: lib/block_scout_web/templates/layout/app.html.eex:45 +#, elixir-autogen, elixir-format +msgid "Less than" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:3 -msgid "Library Name" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 +#, elixir-autogen, elixir-format +msgid "Library" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 +#, elixir-autogen, elixir-format msgid "License Expires" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 +#, elixir-autogen, elixir-format msgid "License ID" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:305 +#, elixir-autogen, elixir-format msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:289 +#, elixir-autogen, elixir-format msgid "List of token burnt in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:272 +#, elixir-autogen, elixir-format msgid "List of token minted in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:256 +#, elixir-autogen, elixir-format msgid "List of token transferred in the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 +#, elixir-autogen, elixir-format msgid "Loading chart..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:70 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:139 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 lib/block_scout_web/templates/address_read_contract/index.html.eex:12 -#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 lib/block_scout_web/templates/address_write_contract/index.html.eex:12 -#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 lib/block_scout_web/templates/tokens/contract/index.html.eex:16 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 +#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 +#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 +#: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "Loading..." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 +#, elixir-autogen, elixir-format msgid "Log Data" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:131 +#, elixir-autogen, elixir-format msgid "Log Index" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:41 -#: lib/block_scout_web/templates/address_logs/index.html.eex:10 lib/block_scout_web/templates/transaction/_tabs.html.eex:17 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 lib/block_scout_web/views/address_view.ex:372 -#: lib/block_scout_web/views/transaction_view.ex:513 +#: lib/block_scout_web/templates/address_logs/index.html.eex:10 +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 +#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 +#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/transaction_view.ex:511 +#, elixir-autogen, elixir-format msgid "Logs" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:52 +#, elixir-autogen, elixir-format msgid "Main Networks" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 -msgid "Mainnet" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:52 -#: lib/block_scout_web/templates/layout/app.html.eex:46 lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 -#: lib/block_scout_web/views/address_view.ex:142 +#: lib/block_scout_web/templates/layout/app.html.eex:46 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 +#: lib/block_scout_web/views/address_view.ex:145 +#, elixir-autogen, elixir-format msgid "Market Cap" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 +#, elixir-autogen, elixir-format +msgid "Market cap" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:382 +#, elixir-autogen, elixir-format msgid "Max Fee per Gas" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:392 +#, elixir-autogen, elixir-format msgid "Max Priority Fee per Gas" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:319 +#: lib/block_scout_web/views/transaction_view.ex:317 +#, elixir-autogen, elixir-format msgid "Max of" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:372 +#, elixir-autogen, elixir-format msgid "Maximum gas amount approved for the transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:381 +#, elixir-autogen, elixir-format msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:196 +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:198 +#, elixir-autogen, elixir-format msgid "Metadata" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 +#, elixir-autogen, elixir-format msgid "Method Id" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/overview.html.eex:98 lib/block_scout_web/templates/chain/_block.html.eex:16 +#: lib/block_scout_web/templates/block/overview.html.eex:98 +#: lib/block_scout_web/templates/chain/_block.html.eex:16 +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:23 +#, elixir-autogen, elixir-format msgid "Miner" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:61 -#: lib/block_scout_web/views/block_view.ex:66 +#: lib/block_scout_web/views/block_view.ex:63 +#: lib/block_scout_web/views/block_view.ex:68 +#, elixir-autogen, elixir-format msgid "Miner Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 +#, elixir-autogen, elixir-format msgid "Minimal Proxy Contract for" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:206 +#, elixir-autogen, elixir-format msgid "Minimum fee required per unit of gas. Fee adjusts based on network congestion." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 +#, elixir-autogen, elixir-format msgid "Model" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 +#, elixir-autogen, elixir-format msgid "Module" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 +#, elixir-autogen, elixir-format msgid "More internal transactions have come in" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:216 lib/block_scout_web/templates/pending_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/chain/show.html.eex:216 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 +#, elixir-autogen, elixir-format msgid "More transactions have come in" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 +#, elixir-autogen, elixir-format msgid "Must be set to:" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:58 +#, elixir-autogen, elixir-format +msgid "N/A" +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:116 +#, elixir-autogen, elixir-format msgid "N/A bytes" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/form.html.eex:19 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:28 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 +#, elixir-autogen, elixir-format msgid "Name" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/form.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Name this API key" +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Name this Custom ABI" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Name this address" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Name this transaction" +msgstr "" + #: lib/block_scout_web/templates/address_token/overview.html.eex:44 +#, elixir-autogen, elixir-format msgid "Net Worth" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 +#, elixir-autogen, elixir-format msgid "New Smart Contract Verification" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 +#, elixir-autogen, elixir-format msgid "New Solidity Smart Contract Verification" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 +#, elixir-autogen, elixir-format +msgid "New Solidity/Yul Smart Contract Verification" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 +#, elixir-autogen, elixir-format msgid "New Vyper Smart Contract Verification" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:73 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 lib/block_scout_web/templates/address_contract_verification/new.html.eex:88 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 +#, elixir-autogen, elixir-format msgid "Next" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:42 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 +#, elixir-autogen, elixir-format msgid "No" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:23 +#, elixir-autogen, elixir-format +msgid "No trace entries found." +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:196 #: lib/block_scout_web/templates/transaction/overview.html.eex:436 +#, elixir-autogen, elixir-format msgid "Nonce" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +#, elixir-autogen, elixir-format msgid "Not unique Token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#, elixir-autogen, elixir-format msgid "Number of accounts holding the token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:290 +#: lib/block_scout_web/templates/address/overview.html.eex:274 +#, elixir-autogen, elixir-format msgid "Number of blocks validated by this validator." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:129 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 +#, elixir-autogen, elixir-format msgid "Number of digits that come after the decimal place when displaying token value" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:201 +#: lib/block_scout_web/templates/address/overview.html.eex:185 +#, elixir-autogen, elixir-format msgid "Number of transactions related to this address." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:117 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 +#, elixir-autogen, elixir-format msgid "Number of transfers for the token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: lib/block_scout_web/templates/address/overview.html.eex:212 +#, elixir-autogen, elixir-format msgid "Number of transfers to/from this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:82 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 +#, elixir-autogen, elixir-format msgid "OUT" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format msgid "Only the first" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:61 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 +#, elixir-autogen, elixir-format +msgid "Optimization" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:62 +#, elixir-autogen, elixir-format msgid "Optimization enabled" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:70 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:58 +#: lib/block_scout_web/templates/address_contract/index.html.eex:71 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 +#, elixir-autogen, elixir-format msgid "Optimization runs" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:76 +#, elixir-autogen, elixir-format msgid "Other Explorers" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 +#, elixir-autogen, elixir-format +msgid "Outgoing" +msgstr "" + #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 +#, elixir-autogen, elixir-format msgid "Owner Address" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 +#, elixir-autogen, elixir-format +msgid "POA solidity flattener or the" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 +#, elixir-autogen, elixir-format msgid "POST" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format msgid "Page" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/page_not_found/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Page not found" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 +#, elixir-autogen, elixir-format msgid "Parameters" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:139 +#, elixir-autogen, elixir-format msgid "Parent Hash" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:54 -#: lib/block_scout_web/views/transaction_view.ex:348 lib/block_scout_web/views/transaction_view.ex:386 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:62 +#: lib/block_scout_web/views/transaction_view.ex:346 +#: lib/block_scout_web/views/transaction_view.ex:384 +#, elixir-autogen, elixir-format msgid "Pending" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 +#, elixir-autogen, elixir-format msgid "Pending Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 +#, elixir-autogen, elixir-format msgid "Play" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 +#, elixir-autogen, elixir-format +msgid "Please select notification methods:" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Please select what types of notifications you will receive:" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:438 +#, elixir-autogen, elixir-format msgid "Position" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:254 +#, elixir-autogen, elixir-format msgid "Position %{index}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 +#, elixir-autogen, elixir-format msgid "Potential matches from our contract method database:" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:27 +#, elixir-autogen, elixir-format msgid "Press / and focus will be moved to the search field" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:41 -#: lib/block_scout_web/templates/layout/app.html.eex:47 lib/block_scout_web/templates/tokens/overview/_details.html.eex:94 +#: lib/block_scout_web/templates/layout/app.html.eex:47 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 +#, elixir-autogen, elixir-format msgid "Price" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:93 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:94 +#, elixir-autogen, elixir-format msgid "Price per token on the exchanges" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:352 +#, elixir-autogen, elixir-format msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:225 #: lib/block_scout_web/templates/transaction/overview.html.eex:402 +#, elixir-autogen, elixir-format msgid "Priority Fee / Tip" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:62 +#, elixir-autogen, elixir-format msgid "Priority Fees" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/common/_nav.html.eex:4 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Profile" +msgstr "" + +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Public Tags" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Public tag" +msgstr "" + +#: lib/block_scout_web/templates/account/common/_nav.html.eex:22 +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Public tags" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:50 +#, elixir-autogen, elixir-format +msgid "Public tags* (2 tags maximum, please use \";\" as a divider)" +msgstr "" + #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 +#, elixir-autogen, elixir-format msgid "QR Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:109 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format msgid "Query" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:98 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#, elixir-autogen, elixir-format msgid "RPC" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:447 +#, elixir-autogen, elixir-format msgid "Raw Input" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 -#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:514 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 +#: lib/block_scout_web/views/transaction_view.ex:512 +#, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:81 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 lib/block_scout_web/views/address_view.ex:366 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 +#: lib/block_scout_web/views/address_view.ex:376 #: lib/block_scout_web/views/tokens/overview_view.ex:41 +#, elixir-autogen, elixir-format msgid "Read Contract" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:88 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 lib/block_scout_web/views/address_view.ex:367 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 +#: lib/block_scout_web/views/address_view.ex:377 +#, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format msgid "Records" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/row.html.eex:13 +#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Remove" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 +#, elixir-autogen, elixir-format +msgid "Remove from Watch list" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 +#, elixir-autogen, elixir-format msgid "Request URL" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:142 +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Request a public tag/label" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:37 +#, elixir-autogen, elixir-format +msgid "Request to add public tag" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Request to edit a public tag/label" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 +#, elixir-autogen, elixir-format msgid "Reset" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 +#, elixir-autogen, elixir-format msgid "Response Body" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 +#, elixir-autogen, elixir-format msgid "Responses" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:92 +#: lib/block_scout_web/templates/transaction/overview.html.eex:98 +#, elixir-autogen, elixir-format msgid "Result" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:129 +#: lib/block_scout_web/templates/transaction/overview.html.eex:135 +#, elixir-autogen, elixir-format msgid "Revert reason" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/chain/_block.html.eex:27 lib/block_scout_web/views/internal_transaction_view.ex:28 +#: lib/block_scout_web/templates/chain/_block.html.eex:27 +#: lib/block_scout_web/views/internal_transaction_view.ex:28 +#, elixir-autogen, elixir-format msgid "Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 +#, elixir-autogen, elixir-format msgid "Run" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/api_key/form.html.eex:26 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 +#: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 +#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 +#, elixir-autogen, elixir-format +msgid "Save" +msgstr "" + #: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/layout/_search.html.eex:34 +#, elixir-autogen, elixir-format msgid "Search" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/search/results.html.eex:17 +#, elixir-autogen, elixir-format msgid "Search Results" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:3 +#, elixir-autogen, elixir-format msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:18 -msgid "Search network" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 +#, elixir-autogen, elixir-format msgid "Search tokens" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Select Yes if you want to vefify Yul contract." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Select yes if you want to show nightly builds." +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:27 +#, elixir-autogen, elixir-format msgid "Self-Destruct" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:63 +#, elixir-autogen, elixir-format +msgid "Send request" +msgstr "" + #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 +#, elixir-autogen, elixir-format msgid "Server Response" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 +#, elixir-autogen, elixir-format msgid "Show" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 +#, elixir-autogen, elixir-format msgid "Show QR Code" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:47 -msgid "Show Validator Info" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format msgid "Shows the current" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:59 +#, elixir-autogen, elixir-format msgid "Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:66 +#, elixir-autogen, elixir-format msgid "Shows the total CRC balance in the address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:45 +#, elixir-autogen, elixir-format msgid "Shows total assets held in the address" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Sign out" +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:114 +#, elixir-autogen, elixir-format msgid "Size" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:113 +#, elixir-autogen, elixir-format msgid "Size of the block in bytes." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Slow" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Smart contract / Address" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:4 +#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Smart contract / Address (0x...)" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 +#: lib/block_scout_web/views/verified_contracts_view.ex:9 +#, elixir-autogen, elixir-format +msgid "Solidity" +msgstr "" + #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_token/index.html.eex:60 lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 lib/block_scout_web/templates/chain/show.html.eex:157 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 lib/block_scout_web/templates/tokens/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:22 lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 +#: lib/block_scout_web/templates/address_logs/index.html.eex:23 +#: lib/block_scout_web/templates/address_token/index.html.eex:60 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 +#: lib/block_scout_web/templates/address_validation/index.html.eex:20 +#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/chain/show.html.eex:157 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 +#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 #: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 lib/block_scout_web/templates/transaction_log/index.html.eex:15 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/transaction_log/index.html.eex:15 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:8 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:45 +#, elixir-autogen, elixir-format msgid "Something went wrong, click to reload." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:222 +#, elixir-autogen, elixir-format msgid "Something went wrong, click to retry." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/not_found.html.eex:7 -msgid "Sorry, We are unable to locate this transaction Hash" +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 +#, elixir-autogen, elixir-format +msgid "Sources *.sol files" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 +#, elixir-autogen, elixir-format +msgid "Sources *.sol or *.yul files" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 +#, elixir-autogen, elixir-format msgid "Sources and Metadata JSON" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:119 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:130 +#, elixir-autogen, elixir-format msgid "Stakes" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 +#, elixir-autogen, elixir-format msgid "Standard Input JSON" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:6 +#: lib/block_scout_web/views/transaction_view.ex:513 +#, elixir-autogen, elixir-format +msgid "State changes" +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:24 +#, elixir-autogen, elixir-format msgid "Static Call" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:104 +#: lib/block_scout_web/templates/transaction/overview.html.eex:110 +#, elixir-autogen, elixir-format msgid "Status" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Submission date" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:39 +#, elixir-autogen, elixir-format msgid "Submit an Issue" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:350 +#: lib/block_scout_web/views/transaction_view.ex:348 +#, elixir-autogen, elixir-format msgid "Success" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:46 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 +#, elixir-autogen, elixir-format msgid "TX Fee" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:30 +#, elixir-autogen, elixir-format msgid "Telegram" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:65 +#, elixir-autogen, elixir-format msgid "Test Networks" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 -msgid "Testnet" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 +#, elixir-autogen, elixir-format +msgid "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 +#, elixir-autogen, elixir-format +msgid "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:122 +#, elixir-autogen, elixir-format msgid "The SHA256 hash of the block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:51 +#, elixir-autogen, elixir-format msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:13 +#, elixir-autogen, elixir-format +msgid "The changes from this transaction have not yet happened since the transaction is still pending." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 +#, elixir-autogen, elixir-format +msgid "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:38 +#, elixir-autogen, elixir-format msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:138 +#, elixir-autogen, elixir-format msgid "The hash of the block from which this block was generated." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:92 +#: lib/block_scout_web/templates/address/overview.html.eex:74 +#, elixir-autogen, elixir-format msgid "The name found in the source code of the Contract." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:103 +#: lib/block_scout_web/templates/address/overview.html.eex:85 +#, elixir-autogen, elixir-format msgid "The name of the validator." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:79 +#, elixir-autogen, elixir-format msgid "The number of transactions in the block." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:40 +#, elixir-autogen, elixir-format msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:128 +#: lib/block_scout_web/templates/transaction/overview.html.eex:134 +#, elixir-autogen, elixir-format msgid "The revert reason of the transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:103 +#: lib/block_scout_web/templates/transaction/overview.html.eex:109 +#, elixir-autogen, elixir-format msgid "The status of the transaction: Confirmed or Unconfirmed." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 +#, elixir-autogen, elixir-format msgid "The total amount of tokens issued" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:177 +#, elixir-autogen, elixir-format msgid "The total gas amount used in the block and its percentage of gas filled in the block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:16 +#, elixir-autogen, elixir-format msgid "There are no blocks validated by this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "There are no blocks." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:28 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 +#, elixir-autogen, elixir-format msgid "There are no holders for this Token." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 +#, elixir-autogen, elixir-format msgid "There are no internal transactions for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 +#, elixir-autogen, elixir-format msgid "There are no internal transactions for this transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "There are no logs for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_log/index.html.eex:20 +#, elixir-autogen, elixir-format msgid "There are no logs for this transaction." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 +#, elixir-autogen, elixir-format msgid "There are no pending transactions." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 +#, elixir-autogen, elixir-format msgid "There are no token transfers for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 +#, elixir-autogen, elixir-format msgid "There are no token transfers for this transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/index.html.eex:65 +#, elixir-autogen, elixir-format msgid "There are no tokens for this address." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27 +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "There are no tokens." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:55 +#, elixir-autogen, elixir-format msgid "There are no transactions for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block_transaction/index.html.eex:27 +#, elixir-autogen, elixir-format msgid "There are no transactions for this block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:31 +#, elixir-autogen, elixir-format msgid "There are no transactions." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 lib/block_scout_web/templates/tokens/transfer/index.html.eex:26 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 +#, elixir-autogen, elixir-format msgid "There are no transfers for this Token." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:93 +#, elixir-autogen, elixir-format +msgid "There are no verified contracts." +msgstr "" + #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 +#, elixir-autogen, elixir-format msgid "There is no coin history for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:29 +#, elixir-autogen, elixir-format msgid "There is no decompilded contracts for this address." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 #: lib/block_scout_web/templates/chain/show.html.eex:9 +#, elixir-autogen, elixir-format msgid "There was a problem loading the chart." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:6 +#, elixir-autogen, elixir-format msgid "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +#, elixir-autogen, elixir-format msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " msgstr "" -#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:11 +#, elixir-autogen, elixir-format msgid "This block has not been processed yet." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:41 +#: lib/block_scout_web/templates/address_contract/index.html.eex:42 +#, elixir-autogen, elixir-format msgid "This contract has been partially verified via Sourcify." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:45 +#: lib/block_scout_web/templates/address_contract/index.html.eex:46 +#, elixir-autogen, elixir-format msgid "This contract has been verified via Sourcify." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +#, elixir-autogen, elixir-format msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:58 +#: lib/block_scout_web/templates/transaction_state/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "This transaction hasn't changed state." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:64 +#, elixir-autogen, elixir-format msgid "This transaction is pending confirmation." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:71 -#: lib/block_scout_web/templates/transaction/overview.html.eex:171 +#: lib/block_scout_web/templates/transaction/overview.html.eex:177 +#, elixir-autogen, elixir-format msgid "Timestamp" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 lib/block_scout_web/templates/address_transaction/index.html.eex:28 -#: lib/block_scout_web/templates/transaction/overview.html.eex:217 lib/block_scout_web/views/address_internal_transaction_view.ex:9 -#: lib/block_scout_web/views/address_token_transfer_view.ex:9 lib/block_scout_web/views/address_transaction_view.ex:9 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:28 +#: lib/block_scout_web/templates/transaction/overview.html.eex:221 +#: lib/block_scout_web/views/address_internal_transaction_view.ex:9 +#: lib/block_scout_web/views/address_token_transfer_view.ex:9 +#: lib/block_scout_web/views/address_transaction_view.ex:9 +#, elixir-autogen, elixir-format msgid "To" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 +#, elixir-autogen, elixir-format msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 +#, elixir-autogen, elixir-format msgid "To see accurate decoded input data, the contract must be verified." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:12 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 +#, elixir-autogen, elixir-format msgid "Toggle navigation" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:73 +#: lib/block_scout_web/templates/address/overview.html.eex:55 +#, elixir-autogen, elixir-format msgid "Token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 lib/block_scout_web/views/transaction_view.ex:448 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 +#: lib/block_scout_web/views/transaction_view.ex:446 +#, elixir-autogen, elixir-format msgid "Token Burning" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 lib/block_scout_web/views/transaction_view.ex:449 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 +#: lib/block_scout_web/views/transaction_view.ex:447 +#, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 +#, elixir-autogen, elixir-format msgid "Token Details" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 lib/block_scout_web/views/tokens/overview_view.ex:40 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 +#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 +#: lib/block_scout_web/views/tokens/overview_view.ex:40 +#, elixir-autogen, elixir-format msgid "Token Holders" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 +#, elixir-autogen, elixir-format msgid "Token ID" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 lib/block_scout_web/views/transaction_view.ex:447 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 +#: lib/block_scout_web/views/transaction_view.ex:445 +#, elixir-autogen, elixir-format msgid "Token Minting" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 lib/block_scout_web/views/transaction_view.ex:450 +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 +#: lib/block_scout_web/views/transaction_view.ex:448 +#, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:13 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 lib/block_scout_web/templates/transaction/_tabs.html.eex:4 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 lib/block_scout_web/views/address_view.ex:363 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:195 lib/block_scout_web/views/tokens/overview_view.ex:39 -#: lib/block_scout_web/views/transaction_view.ex:511 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 +#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 +#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 +#: lib/block_scout_web/views/address_view.ex:373 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:197 +#: lib/block_scout_web/views/tokens/overview_view.ex:39 +#: lib/block_scout_web/views/transaction_view.ex:509 +#, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:72 +#: lib/block_scout_web/templates/address/overview.html.eex:54 +#, elixir-autogen, elixir-format msgid "Token name and symbol." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 +#, elixir-autogen, elixir-format msgid "Token type" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:191 lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 lib/block_scout_web/templates/layout/_topnav.html.eex:67 -#: lib/block_scout_web/templates/tokens/index.html.eex:10 lib/block_scout_web/views/address_view.ex:360 +#: lib/block_scout_web/templates/address/overview.html.eex:175 +#: lib/block_scout_web/templates/address_token/overview.html.eex:58 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +#: lib/block_scout_web/templates/tokens/index.html.eex:10 +#: lib/block_scout_web/views/address_view.ex:370 +#, elixir-autogen, elixir-format msgid "Tokens" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:290 +#, elixir-autogen, elixir-format msgid "Tokens Burnt" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:306 +#, elixir-autogen, elixir-format msgid "Tokens Created" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:273 +#, elixir-autogen, elixir-format msgid "Tokens Minted" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:257 +#, elixir-autogen, elixir-format msgid "Tokens Transferred" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:13 +#, elixir-autogen, elixir-format msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 +#, elixir-autogen, elixir-format msgid "Topic" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 +#, elixir-autogen, elixir-format msgid "Topics" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Total" +msgstr "" + #: lib/block_scout_web/templates/block/overview.html.eex:169 +#, elixir-autogen, elixir-format msgid "Total Difficulty" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 +#, elixir-autogen, elixir-format msgid "Total Supply * Price" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:130 +#, elixir-autogen, elixir-format msgid "Total blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:168 +#, elixir-autogen, elixir-format msgid "Total difficulty of the chain until this block." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:186 +#, elixir-autogen, elixir-format msgid "Total gas limit provided by all transactions in the block." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:69 +#, elixir-autogen, elixir-format msgid "Total supply" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:337 +#, elixir-autogen, elixir-format msgid "Total transaction fee." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:109 +#, elixir-autogen, elixir-format msgid "Total transactions" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:460 +#: lib/block_scout_web/views/transaction_view.ex:458 +#, elixir-autogen, elixir-format msgid "Transaction" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 +#, elixir-autogen, elixir-format msgid "Transaction %{transaction} - %{subnetwork} Explorer" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 +#, elixir-autogen, elixir-format msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:412 +#, elixir-autogen, elixir-format msgid "Transaction Burnt Fee" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:49 +#: lib/block_scout_web/templates/transaction/overview.html.eex:50 +#, elixir-autogen, elixir-format msgid "Transaction Details" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:338 +#, elixir-autogen, elixir-format msgid "Transaction Fee" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:74 +#: lib/block_scout_web/templates/transaction/overview.html.eex:80 +#, elixir-autogen, elixir-format msgid "Transaction Hash" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 +#, elixir-autogen, elixir-format msgid "Transaction Inputs" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/common/_nav.html.eex:13 +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 +#, elixir-autogen, elixir-format +msgid "Transaction Tags" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:362 +#, elixir-autogen, elixir-format msgid "Transaction Type" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:435 +#, elixir-autogen, elixir-format msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:361 +#, elixir-autogen, elixir-format msgid "Transaction type, introduced in EIP-2718." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:7 -#: lib/block_scout_web/templates/address/overview.html.eex:202 lib/block_scout_web/templates/address/overview.html.eex:208 -#: lib/block_scout_web/templates/address/overview.html.eex:216 lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/block/overview.html.eex:80 lib/block_scout_web/templates/block_transaction/index.html.eex:10 -#: lib/block_scout_web/templates/chain/show.html.eex:213 lib/block_scout_web/templates/layout/_topnav.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:362 +#: lib/block_scout_web/templates/address/overview.html.eex:186 +#: lib/block_scout_web/templates/address/overview.html.eex:192 +#: lib/block_scout_web/templates/address/overview.html.eex:200 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/block/overview.html.eex:80 +#: lib/block_scout_web/templates/block_transaction/index.html.eex:10 +#: lib/block_scout_web/templates/chain/show.html.eex:213 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 +#: lib/block_scout_web/views/address_view.ex:372 +#, elixir-autogen, elixir-format msgid "Transactions" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:119 +#: lib/block_scout_web/templates/address/overview.html.eex:101 +#, elixir-autogen, elixir-format msgid "Transactions and address of creation." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tile.html.eex:31 +#, elixir-autogen, elixir-format msgid "Transactions sent" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/address/overview.html.eex:235 lib/block_scout_web/templates/address/overview.html.eex:243 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 +#: lib/block_scout_web/templates/address/overview.html.eex:213 +#: lib/block_scout_web/templates/address/overview.html.eex:219 +#: lib/block_scout_web/templates/address/overview.html.eex:227 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 +#, elixir-autogen, elixir-format msgid "Transfers" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 +#, elixir-autogen, elixir-format msgid "Try it out" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Try to fetch constructor arguments automatically" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:27 +#, elixir-autogen, elixir-format msgid "Twitter" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/layout/app.html.eex:49 +#, elixir-autogen, elixir-format msgid "Tx/day" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 +#, elixir-autogen, elixir-format +msgid "Txns" +msgstr "" + #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 +#, elixir-autogen, elixir-format msgid "Type" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:140 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 +#, elixir-autogen, elixir-format msgid "Type of the token standard" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 +#, elixir-autogen, elixir-format +msgid "UML diagram" +msgstr "" + #: lib/block_scout_web/templates/transaction/overview.html.eex:461 +#, elixir-autogen, elixir-format msgid "UTF-8" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/block_view.ex:75 +#: lib/block_scout_web/views/block_view.ex:77 +#, elixir-autogen, elixir-format msgid "Uncle Reward" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:250 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:30 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 +#, elixir-autogen, elixir-format msgid "Uncles" msgstr "" -#, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:341 +#: lib/block_scout_web/views/transaction_view.ex:339 +#, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 +#, elixir-autogen, elixir-format msgid "Unique Token" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:73 +#: lib/block_scout_web/templates/transaction/overview.html.eex:79 +#, elixir-autogen, elixir-format msgid "Unique character string (TxID) assigned to every verified transaction." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12 -msgid "Use the search box to find a hosted network, or select from the list of available networks below." +#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 +#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Update" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:391 +#, elixir-autogen, elixir-format msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:401 +#, elixir-autogen, elixir-format msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:224 +#, elixir-autogen, elixir-format msgid "User-defined tips sent to validator for transaction priority/inclusion." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:46 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#, elixir-autogen, elixir-format msgid "Validated" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:12 +#, elixir-autogen, elixir-format msgid "Validated Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 +#, elixir-autogen, elixir-format msgid "Validator Creation Date" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 +#, elixir-autogen, elixir-format msgid "Validator Data" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:51 -msgid "Validator Info" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:104 +#: lib/block_scout_web/templates/address/overview.html.eex:86 +#, elixir-autogen, elixir-format msgid "Validator Name" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:323 +#, elixir-autogen, elixir-format msgid "Value" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:322 +#, elixir-autogen, elixir-format msgid "Value sent in the native token (and USD) if applicable." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:82 +#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Verifed contracts, %{subnetwork}, %{coin}" +msgstr "" + +#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 +#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 +#, elixir-autogen, elixir-format +msgid "Verified" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Verified Contracts" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:83 +#, elixir-autogen, elixir-format msgid "Verified at" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:160 -#: lib/block_scout_web/templates/address_contract/index.html.eex:166 lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:203 lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:68 +#, elixir-autogen, elixir-format +msgid "Verified contracts" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Verified contracts - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#: lib/block_scout_web/templates/address_contract/index.html.eex:30 +#: lib/block_scout_web/templates/address_contract/index.html.eex:192 +#: lib/block_scout_web/templates/address_contract/index.html.eex:223 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 +#, elixir-autogen, elixir-format msgid "Verify & Publish" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:141 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 +#, elixir-autogen, elixir-format msgid "Verify & publish" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#, elixir-autogen, elixir-format msgid "Verify the contract " msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_footer.html.eex:91 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 +#, elixir-autogen, elixir-format msgid "Version" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 +#, elixir-autogen, elixir-format msgid "Via Sourcify: Sources and metadata JSON file" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 +#, elixir-autogen, elixir-format msgid "Via Standard Input JSON" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 +#, elixir-autogen, elixir-format msgid "Via flattened source code" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 +#, elixir-autogen, elixir-format +msgid "Via multi-part files" +msgstr "" + #: lib/block_scout_web/templates/chain/show.html.eex:152 +#, elixir-autogen, elixir-format msgid "View All Blocks" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:212 +#, elixir-autogen, elixir-format msgid "View All Transactions" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 +#, elixir-autogen, elixir-format msgid "View Contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:67 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 +#, elixir-autogen, elixir-format msgid "View Less Transfers" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:66 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 +#, elixir-autogen, elixir-format msgid "View More Transfers" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:39 +#, elixir-autogen, elixir-format msgid "View next block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:23 +#, elixir-autogen, elixir-format msgid "View previous block" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:9 +#, elixir-autogen, elixir-format msgid "View the account balance, transactions, and other data for %{address} on the %{network}" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:10 +#, elixir-autogen, elixir-format msgid "View the transactions, token transfers, and uncles for block number %{block_number}" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 +#, elixir-autogen, elixir-format +msgid "View the verified contracts on %{subnetwork}" +msgstr "" + #: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 +#, elixir-autogen, elixir-format msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:39 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 +#: lib/block_scout_web/views/verified_contracts_view.ex:10 +#, elixir-autogen, elixir-format +msgid "Vyper" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 +#, elixir-autogen, elixir-format msgid "Vyper contract" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:145 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:136 +#, elixir-autogen, elixir-format msgid "WEI" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 +#, elixir-autogen, elixir-format msgid "Waiting for transaction's confirmation..." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:138 +#, elixir-autogen, elixir-format msgid "Wallet addresses" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 +#, elixir-autogen, elixir-format msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky." msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/account/common/_nav.html.eex:7 +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 +#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Watch list" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 +#, elixir-autogen, elixir-format +msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" +msgstr "" + #: lib/block_scout_web/views/wei_helpers.ex:76 +#, elixir-autogen, elixir-format msgid "Wei" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:109 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format msgid "Write" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:95 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 lib/block_scout_web/views/address_view.ex:368 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 +#: lib/block_scout_web/views/address_view.ex:378 +#, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:102 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 lib/block_scout_web/views/address_view.ex:369 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 +#: lib/block_scout_web/views/address_view.ex:379 +#, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:47 +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 +#, elixir-autogen, elixir-format msgid "Yes" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:129 +#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "You can create 3 API keys per account." +msgstr "" + +#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "You can create up to 15 Custom ABIs per account." +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:11 +#, elixir-autogen, elixir-format +msgid "You can request a public category tag which is displayed to all Blockscout users. Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag. Clicking a tag opens a page with related information and helps provide context and data organization. Requests are sent to a moderator for review and approval. This process can take several days." +msgstr "" + +#: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "You don't have address tags yet" +msgstr "" + +#: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 +#, elixir-autogen, elixir-format +msgid "You don't have addresses on you watchlist yet" +msgstr "" + +#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "You don't have transaction tags yet" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Your name" +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Your name*" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:111 +#, elixir-autogen, elixir-format msgid "at" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format msgid "balance of the address" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:411 +#, elixir-autogen, elixir-format msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." msgstr "" -#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#, elixir-autogen, elixir-format msgid "button" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:228 +#: lib/block_scout_web/templates/transaction/overview.html.eex:230 +#, elixir-autogen, elixir-format msgid "created" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +#, elixir-autogen, elixir-format msgid "custom RPC" msgstr "" -#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:149 +#, elixir-autogen, elixir-format +msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." +msgstr "" + #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format msgid "elements are displayed" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:38 +#, elixir-autogen, elixir-format msgid "fallback" msgstr "" -#, elixir-format #: lib/block_scout_web/views/address_contract_view.ex:24 +#, elixir-autogen, elixir-format msgid "false" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 +#, elixir-autogen, elixir-format msgid "here" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +#, elixir-autogen, elixir-format msgid "here." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/invalid.html.eex:8 -msgid "is not a valid transaction hash" -msgstr "" - -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 +#, elixir-autogen, elixir-format msgid "method Response" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format msgid "of" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 +#, elixir-autogen, elixir-format msgid "page" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:40 +#, elixir-autogen, elixir-format msgid "receive" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 +#, elixir-autogen, elixir-format msgid "required" msgstr "" -#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 +#, elixir-autogen, elixir-format msgid "string" msgstr "" -#, elixir-format #: lib/block_scout_web/views/address_contract_view.ex:23 +#, elixir-autogen, elixir-format msgid "true" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:9 -msgid "Error in internal transactions" +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 +#, elixir-autogen, elixir-format +msgid "truffle flattener" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -msgid "Average" +#: lib/block_scout_web/templates/transaction/not_found.html.eex:8 +#, elixir-autogen, elixir-format, fuzzy +msgid "1. If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/show.html.eex:69 -msgid "Daily Transactions" +#: lib/block_scout_web/templates/transaction/not_found.html.eex:9 +#, elixir-autogen, elixir-format, fuzzy +msgid "2. It could still be in the TX Pool of a different node, waiting to be broadcasted." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -msgid "Fast" +#: lib/block_scout_web/templates/transaction/not_found.html.eex:11 +#, elixir-autogen, elixir-format, fuzzy +msgid "4. If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 -msgid "Gas tracker" +#: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "An unexpected error has occurred. Try reloading the page, or come back soon and try again." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -msgid "Slow" +#: lib/block_scout_web/templates/error422/index.html.eex:9 +#: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 +#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 +#: lib/block_scout_web/templates/transaction/not_found.html.eex:13 +#, elixir-autogen, elixir-format, fuzzy +msgid "Back to home" +msgstr "" + +#: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Internal server error" +msgstr "" + +#: lib/block_scout_web/templates/error422/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Request cannot be processed" +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:6 +#, elixir-autogen, elixir-format, fuzzy +msgid "Sorry, we are unable to locate this transaction hash" +msgstr "" + +#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for." +msgstr "" + +#: lib/block_scout_web/templates/error422/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:10 +#, elixir-autogen, elixir-format, fuzzy +msgid "3. During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs new file mode 100644 index 000000000000..0081ebdcfb72 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs @@ -0,0 +1,948 @@ +defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.Address + alias BlockScoutWeb.Models.UserFromAuth + + setup %{conn: conn} do + auth = build(:auth) + + {:ok, user} = UserFromAuth.find_or_create(auth) + + {:ok, user: user, conn: Plug.Test.init_test_session(conn, current_user: user)} + end + + describe "Test account/api/v1/user" do + test "get user info", %{conn: conn, user: user} do + result_conn = + conn + |> get("/api/account/v1/user/info") + |> doc(description: "Get info about user") + + assert json_response(result_conn, 200) == %{ + "nickname" => user.nickname, + "name" => user.name, + "email" => user.email, + "avatar" => user.avatar + } + end + + test "post private address tag", %{conn: conn} do + tag_address_response = + conn + |> post("/api/account/v1/user/tags/address", %{ + "address_hash" => "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b", + "name" => "MyName" + }) + |> doc(description: "Add private address tag") + |> json_response(200) + + conn + |> get("/api/account/v1/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b") + |> doc(description: "Get tags for address") + |> json_response(200) + + assert tag_address_response["address_hash"] == "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b" + assert tag_address_response["name"] == "MyName" + assert tag_address_response["id"] + end + + test "edit private address tag", %{conn: conn} do + address_tag = build(:tag_address) + + tag_address_response = + conn + |> post("/api/account/v1/user/tags/address", address_tag) + |> json_response(200) + + _response = + conn + |> get("/api/account/v1/user/tags/address") + |> json_response(200) == [tag_address_response] + + assert tag_address_response["address_hash"] == address_tag["address_hash"] + assert tag_address_response["name"] == address_tag["name"] + assert tag_address_response["id"] + + new_address_tag = build(:tag_address) + + new_tag_address_response = + conn + |> put("/api/account/v1/user/tags/address/#{tag_address_response["id"]}", new_address_tag) + |> doc(description: "Edit private address tag") + |> json_response(200) + + assert new_tag_address_response["address_hash"] == new_address_tag["address_hash"] + assert new_tag_address_response["name"] == new_address_tag["name"] + assert new_tag_address_response["id"] == tag_address_response["id"] + end + + test "get address tags after inserting one private tags", %{conn: conn} do + addresses = Enum.map(0..2, fn _x -> to_string(build(:address).hash) end) + names = Enum.map(0..2, fn x -> "name#{x}" end) + zipped = Enum.zip(addresses, names) + + created = + Enum.map(zipped, fn {addr, name} -> + id = + (conn + |> post("/api/account/v1/user/tags/address", %{ + "address_hash" => addr, + "name" => name + }) + |> json_response(200))["id"] + + {addr, %{"display_name" => name, "label" => name, "address_hash" => addr}, + %{ + "address_hash" => addr, + "id" => id, + "name" => name, + "address" => %{ + "hash" => Address.checksum(addr), + "implementation_name" => nil, + "is_contract" => false, + "is_verified" => false, + "name" => nil, + "private_tags" => [], + "public_tags" => [], + "watchlist_names" => [] + } + }} + end) + + assert Enum.all?(created, fn {addr, map_tag, _} -> + response = + conn + |> get("/api/account/v1/tags/address/#{addr}") + |> json_response(200) + + response["personal_tags"] == [map_tag] + end) + + response = + conn + |> get("/api/account/v1/user/tags/address") + |> doc(description: "Get private addresses tags") + |> json_response(200) + + assert Enum.all?(created, fn {_, _, map} -> map in response end) + end + + test "delete address tag", %{conn: conn} do + addresses = Enum.map(0..2, fn _x -> to_string(build(:address).hash) end) + names = Enum.map(0..2, fn x -> "name#{x}" end) + zipped = Enum.zip(addresses, names) + + created = + Enum.map(zipped, fn {addr, name} -> + id = + (conn + |> post("/api/account/v1/user/tags/address", %{ + "address_hash" => addr, + "name" => name + }) + |> json_response(200))["id"] + + {addr, %{"display_name" => name, "label" => name, "address_hash" => addr}, + %{ + "address_hash" => addr, + "id" => id, + "name" => name, + "address" => %{ + "hash" => Address.checksum(addr), + "implementation_name" => nil, + "is_contract" => false, + "is_verified" => false, + "name" => nil, + "private_tags" => [], + "public_tags" => [], + "watchlist_names" => [] + } + }} + end) + + assert Enum.all?(created, fn {addr, map_tag, _} -> + response = + conn + |> get("/api/account/v1/tags/address/#{addr}") + |> json_response(200) + + response["personal_tags"] == [map_tag] + end) + + response = + conn + |> get("/api/account/v1/user/tags/address") + |> json_response(200) + + assert Enum.all?(created, fn {_, _, map} -> map in response end) + + {_, _, %{"id" => id}} = Enum.at(created, 0) + + assert conn + |> delete("/api/account/v1/user/tags/address/#{id}") + |> doc("Delete private address tag") + |> json_response(200) == %{"message" => "OK"} + + assert Enum.all?(Enum.drop(created, 1), fn {_, _, %{"id" => id}} -> + conn + |> delete("/api/account/v1/user/tags/address/#{id}") + |> json_response(200) == %{"message" => "OK"} + end) + + assert conn + |> get("/api/account/v1/user/tags/address") + |> json_response(200) == [] + + assert Enum.all?(created, fn {addr, _, _} -> + response = + conn + |> get("/api/account/v1/tags/address/#{addr}") + |> json_response(200) + + response["personal_tags"] == [] + end) + end + + test "post private transaction tag", %{conn: conn} do + tx_hash_non_existing = to_string(build(:transaction).hash) + tx_hash = to_string(insert(:transaction).hash) + + assert conn + |> post("/api/account/v1/user/tags/transaction", %{ + "transaction_hash" => tx_hash_non_existing, + "name" => "MyName" + }) + |> doc(description: "Error on try to create private transaction tag for tx does not exist") + |> json_response(422) == %{"errors" => %{"tx_hash" => ["Transaction does not exist"]}} + + tag_transaction_response = + conn + |> post("/api/account/v1/user/tags/transaction", %{ + "transaction_hash" => tx_hash, + "name" => "MyName" + }) + |> doc(description: "Create private transaction tag") + |> json_response(200) + + conn + |> get("/api/account/v1/tags/transaction/#{tx_hash}") + |> doc(description: "Get tags for transaction") + |> json_response(200) + + assert tag_transaction_response["transaction_hash"] == tx_hash + assert tag_transaction_response["name"] == "MyName" + assert tag_transaction_response["id"] + end + + test "edit private transaction tag", %{conn: conn} do + tx_tag = build(:tag_transaction) + + tag_response = + conn + |> post("/api/account/v1/user/tags/transaction", tx_tag) + |> json_response(200) + + _response = + conn + |> get("/api/account/v1/user/tags/transaction") + |> json_response(200) == [tag_response] + + assert tag_response["address_hash"] == tx_tag["address_hash"] + assert tag_response["name"] == tx_tag["name"] + assert tag_response["id"] + + new_tx_tag = build(:tag_transaction) + + new_tag_response = + conn + |> put("/api/account/v1/user/tags/transaction/#{tag_response["id"]}", new_tx_tag) + |> doc(description: "Edit private transaction tag") + |> json_response(200) + + assert new_tag_response["address_hash"] == new_tx_tag["address_hash"] + assert new_tag_response["name"] == new_tx_tag["name"] + assert new_tag_response["id"] == tag_response["id"] + end + + test "get transaction tags after inserting one private tags", %{conn: conn} do + transactions = Enum.map(0..2, fn _x -> to_string(insert(:transaction).hash) end) + names = Enum.map(0..2, fn x -> "name#{x}" end) + zipped = Enum.zip(transactions, names) + + created = + Enum.map(zipped, fn {tx_hash, name} -> + id = + (conn + |> post("/api/account/v1/user/tags/transaction", %{ + "transaction_hash" => tx_hash, + "name" => name + }) + |> json_response(200))["id"] + + {tx_hash, %{"label" => name}, %{"transaction_hash" => tx_hash, "id" => id, "name" => name}} + end) + + assert Enum.all?(created, fn {tx_hash, map_tag, _} -> + response = + conn + |> get("/api/account/v1/tags/transaction/#{tx_hash}") + |> json_response(200) + + response["personal_tx_tag"] == map_tag + end) + + response = + conn + |> get("/api/account/v1/user/tags/transaction") + |> doc(description: "Get private transactions tags") + |> json_response(200) + + assert Enum.all?(created, fn {_, _, map} -> map in response end) + end + + test "delete transaction tag", %{conn: conn} do + transactions = Enum.map(0..2, fn _x -> to_string(insert(:transaction).hash) end) + names = Enum.map(0..2, fn x -> "name#{x}" end) + zipped = Enum.zip(transactions, names) + + created = + Enum.map(zipped, fn {tx_hash, name} -> + id = + (conn + |> post("/api/account/v1/user/tags/transaction", %{ + "transaction_hash" => tx_hash, + "name" => name + }) + |> json_response(200))["id"] + + {tx_hash, %{"label" => name}, %{"transaction_hash" => tx_hash, "id" => id, "name" => name}} + end) + + assert Enum.all?(created, fn {tx_hash, map_tag, _} -> + response = + conn + |> get("/api/account/v1/tags/transaction/#{tx_hash}") + |> json_response(200) + + response["personal_tx_tag"] == map_tag + end) + + response = + conn + |> get("/api/account/v1/user/tags/transaction") + |> json_response(200) + + assert Enum.all?(created, fn {_, _, map} -> map in response end) + + {_, _, %{"id" => id}} = Enum.at(created, 0) + + assert conn + |> delete("/api/account/v1/user/tags/transaction/#{id}") + |> doc("Delete private transaction tag") + |> json_response(200) == %{"message" => "OK"} + + assert Enum.all?(Enum.drop(created, 1), fn {_, _, %{"id" => id}} -> + conn + |> delete("/api/account/v1/user/tags/transaction/#{id}") + |> json_response(200) == %{"message" => "OK"} + end) + + assert conn + |> get("/api/account/v1/user/tags/transaction") + |> json_response(200) == [] + + assert Enum.all?(created, fn {addr, _, _} -> + response = + conn + |> get("/api/account/v1/tags/transaction/#{addr}") + |> json_response(200) + + response["personal_tx_tag"] == nil + end) + end + + test "post && get watchlist address", %{conn: conn} do + watchlist_address_map = build(:watchlist_address) + + post_watchlist_address_response = + conn + |> post( + "/api/account/v1/user/watchlist", + watchlist_address_map + ) + |> doc(description: "Add address to watch list") + |> json_response(200) + + assert post_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] + assert post_watchlist_address_response["name"] == watchlist_address_map["name"] + assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] + assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] + + get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + + assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] + assert get_watchlist_address_response["name"] == watchlist_address_map["name"] + assert get_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] + assert get_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] + assert get_watchlist_address_response["id"] == post_watchlist_address_response["id"] + + watchlist_address_map_1 = build(:watchlist_address) + + post_watchlist_address_response_1 = + conn + |> post( + "/api/account/v1/user/watchlist", + watchlist_address_map_1 + ) + |> json_response(200) + + get_watchlist_address_response_1_0 = + conn + |> get("/api/account/v1/user/watchlist") + |> doc(description: "Get addresses from watchlists") + |> json_response(200) + |> Enum.at(1) + + get_watchlist_address_response_1_1 = + conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + + assert get_watchlist_address_response_1_0 == get_watchlist_address_response + + assert get_watchlist_address_response_1_1["notification_settings"] == + watchlist_address_map_1["notification_settings"] + + assert get_watchlist_address_response_1_1["name"] == watchlist_address_map_1["name"] + + assert get_watchlist_address_response_1_1["notification_methods"] == + watchlist_address_map_1["notification_methods"] + + assert get_watchlist_address_response_1_1["address_hash"] == watchlist_address_map_1["address_hash"] + assert get_watchlist_address_response_1_1["id"] == post_watchlist_address_response_1["id"] + end + + test "delete watchlist address", %{conn: conn} do + watchlist_address_map = build(:watchlist_address) + + post_watchlist_address_response = + conn + |> post( + "/api/account/v1/user/watchlist", + watchlist_address_map + ) + |> json_response(200) + + assert post_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] + assert post_watchlist_address_response["name"] == watchlist_address_map["name"] + assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] + assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] + + get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + + assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] + assert get_watchlist_address_response["name"] == watchlist_address_map["name"] + assert get_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] + assert get_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] + assert get_watchlist_address_response["id"] == post_watchlist_address_response["id"] + + watchlist_address_map_1 = build(:watchlist_address) + + post_watchlist_address_response_1 = + conn + |> post( + "/api/account/v1/user/watchlist", + watchlist_address_map_1 + ) + |> json_response(200) + + get_watchlist_address_response_1_0 = + conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(1) + + get_watchlist_address_response_1_1 = + conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + + assert get_watchlist_address_response_1_0 == get_watchlist_address_response + + assert get_watchlist_address_response_1_1["notification_settings"] == + watchlist_address_map_1["notification_settings"] + + assert get_watchlist_address_response_1_1["name"] == watchlist_address_map_1["name"] + + assert get_watchlist_address_response_1_1["notification_methods"] == + watchlist_address_map_1["notification_methods"] + + assert get_watchlist_address_response_1_1["address_hash"] == watchlist_address_map_1["address_hash"] + assert get_watchlist_address_response_1_1["id"] == post_watchlist_address_response_1["id"] + + assert conn + |> delete("/api/account/v1/user/watchlist/#{get_watchlist_address_response_1_1["id"]}") + |> doc(description: "Delete address from watchlist by id") + |> json_response(200) == %{"message" => "OK"} + + assert conn + |> delete("/api/account/v1/user/watchlist/#{get_watchlist_address_response_1_0["id"]}") + |> json_response(200) == %{"message" => "OK"} + + assert conn |> get("/api/account/v1/user/watchlist") |> json_response(200) == [] + end + + test "put watchlist address", %{conn: conn} do + watchlist_address_map = build(:watchlist_address) + + post_watchlist_address_response = + conn + |> post( + "/api/account/v1/user/watchlist", + watchlist_address_map + ) + |> json_response(200) + + assert post_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] + assert post_watchlist_address_response["name"] == watchlist_address_map["name"] + assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] + assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] + + get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + + assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] + assert get_watchlist_address_response["name"] == watchlist_address_map["name"] + assert get_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] + assert get_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] + assert get_watchlist_address_response["id"] == post_watchlist_address_response["id"] + + new_watchlist_address_map = build(:watchlist_address) + + put_watchlist_address_response = + conn + |> put( + "/api/account/v1/user/watchlist/#{post_watchlist_address_response["id"]}", + new_watchlist_address_map + ) + |> doc(description: "Edit watchlist address") + |> json_response(200) + + assert put_watchlist_address_response["notification_settings"] == + new_watchlist_address_map["notification_settings"] + + assert put_watchlist_address_response["name"] == new_watchlist_address_map["name"] + assert put_watchlist_address_response["notification_methods"] == new_watchlist_address_map["notification_methods"] + assert put_watchlist_address_response["address_hash"] == new_watchlist_address_map["address_hash"] + assert get_watchlist_address_response["id"] == put_watchlist_address_response["id"] + end + + test "cannot create duplicate of watchlist address", %{conn: conn} do + watchlist_address_map = build(:watchlist_address) + + post_watchlist_address_response = + conn + |> post( + "/api/account/v1/user/watchlist", + watchlist_address_map + ) + |> json_response(200) + + assert post_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] + assert post_watchlist_address_response["name"] == watchlist_address_map["name"] + assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] + assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] + + assert conn + |> post( + "/api/account/v1/user/watchlist", + watchlist_address_map + ) + |> doc(description: "Example of error on creating watchlist address") + |> json_response(422) == %{"errors" => %{"watchlist_id" => ["Address already added to the watch list"]}} + + new_watchlist_address_map = build(:watchlist_address) + + post_watchlist_address_response_1 = + conn + |> post( + "/api/account/v1/user/watchlist", + new_watchlist_address_map + ) + |> json_response(200) + + assert conn + |> put( + "/api/account/v1/user/watchlist/#{post_watchlist_address_response_1["id"]}", + watchlist_address_map + ) + |> doc(description: "Example of error on editing watchlist address") + |> json_response(422) == %{"errors" => %{"watchlist_id" => ["Address already added to the watch list"]}} + end + + test "post api key", %{conn: conn} do + post_api_key_response = + conn + |> post( + "/api/account/v1/user/api_keys", + %{"name" => "test"} + ) + |> doc(description: "Add api key") + |> json_response(200) + + assert post_api_key_response["name"] == "test" + assert post_api_key_response["api_key"] + end + + test "can create not more than 3 api keys + get api keys", %{conn: conn} do + Enum.each(0..2, fn _x -> + conn + |> post( + "/api/account/v1/user/api_keys", + %{"name" => "test"} + ) + |> json_response(200) + end) + + assert conn + |> post( + "/api/account/v1/user/api_keys", + %{"name" => "test"} + ) + |> doc(description: "Example of error on creating api key") + |> json_response(422) == %{"errors" => %{"name" => ["Max 3 keys per account"]}} + + assert conn + |> get("/api/account/v1/user/api_keys") + |> doc(description: "Get api keys list") + |> json_response(200) + |> Enum.count() == 3 + end + + test "edit api key", %{conn: conn} do + post_api_key_response = + conn + |> post( + "/api/account/v1/user/api_keys", + %{"name" => "test"} + ) + |> json_response(200) + + assert post_api_key_response["name"] == "test" + assert post_api_key_response["api_key"] + + put_api_key_response = + conn + |> put( + "/api/account/v1/user/api_keys/#{post_api_key_response["api_key"]}", + %{"name" => "test_1"} + ) + |> doc(description: "Edit api key") + |> json_response(200) + + assert put_api_key_response["api_key"] == post_api_key_response["api_key"] + assert put_api_key_response["name"] == "test_1" + + assert conn + |> get("/api/account/v1/user/api_keys") + |> json_response(200) == [put_api_key_response] + end + + test "delete api key", %{conn: conn} do + post_api_key_response = + conn + |> post( + "/api/account/v1/user/api_keys", + %{"name" => "test"} + ) + |> json_response(200) + + assert post_api_key_response["name"] == "test" + assert post_api_key_response["api_key"] + + assert conn + |> get("/api/account/v1/user/api_keys") + |> json_response(200) + |> Enum.count() == 1 + + assert conn + |> delete("/api/account/v1/user/api_keys/#{post_api_key_response["api_key"]}") + |> doc(description: "Delete api key") + |> json_response(200) == %{"message" => "OK"} + + assert conn + |> get("/api/account/v1/user/api_keys") + |> json_response(200) == [] + end + + test "post custom abi", %{conn: conn} do + custom_abi = build(:custom_abi) + + post_custom_abi_response = + conn + |> post( + "/api/account/v1/user/custom_abis", + custom_abi + ) + |> doc(description: "Add custom abi") + |> json_response(200) + + assert post_custom_abi_response["name"] == custom_abi["name"] + assert post_custom_abi_response["abi"] == custom_abi["abi"] + assert post_custom_abi_response["contract_address_hash"] == custom_abi["contract_address_hash"] + assert post_custom_abi_response["id"] + end + + test "can create not more than 15 custom abis + get custom abi", %{conn: conn} do + Enum.each(0..14, fn _x -> + conn + |> post( + "/api/account/v1/user/custom_abis", + build(:custom_abi) + ) + |> json_response(200) + end) + + assert conn + |> post( + "/api/account/v1/user/custom_abis", + build(:custom_abi) + ) + |> doc(description: "Example of error on creating custom abi") + |> json_response(422) == %{"errors" => %{"name" => ["Max 15 ABIs per account"]}} + + assert conn + |> get("/api/account/v1/user/custom_abis") + |> doc(description: "Get custom abis list") + |> json_response(200) + |> Enum.count() == 15 + end + + test "edit custom abi", %{conn: conn} do + custom_abi = build(:custom_abi) + + post_custom_abi_response = + conn + |> post( + "/api/account/v1/user/custom_abis", + custom_abi + ) + |> json_response(200) + + assert post_custom_abi_response["name"] == custom_abi["name"] + assert post_custom_abi_response["abi"] == custom_abi["abi"] + assert post_custom_abi_response["contract_address_hash"] == custom_abi["contract_address_hash"] + assert post_custom_abi_response["id"] + + custom_abi_1 = build(:custom_abi) + + put_custom_abi_response = + conn + |> put( + "/api/account/v1/user/custom_abis/#{post_custom_abi_response["id"]}", + custom_abi_1 + ) + |> doc(description: "Edit custom abi") + |> json_response(200) + + assert put_custom_abi_response["name"] == custom_abi_1["name"] + assert put_custom_abi_response["id"] == post_custom_abi_response["id"] + assert put_custom_abi_response["contract_address_hash"] == custom_abi_1["contract_address_hash"] + assert put_custom_abi_response["abi"] == custom_abi_1["abi"] + + assert conn + |> get("/api/account/v1/user/custom_abis") + |> json_response(200) == [put_custom_abi_response] + end + + test "delete custom abi", %{conn: conn} do + custom_abi = build(:custom_abi) + + post_custom_abi_response = + conn + |> post( + "/api/account/v1/user/custom_abis", + custom_abi + ) + |> json_response(200) + + assert post_custom_abi_response["name"] == custom_abi["name"] + assert post_custom_abi_response["id"] + + assert conn + |> get("/api/account/v1/user/custom_abis") + |> json_response(200) + |> Enum.count() == 1 + + assert conn + |> delete("/api/account/v1/user/custom_abis/#{post_custom_abi_response["id"]}") + |> doc(description: "Delete custom abi") + |> json_response(200) == %{"message" => "OK"} + + assert conn + |> get("/api/account/v1/user/custom_abis") + |> json_response(200) == [] + end + end + + describe "public tags" do + test "create public tags reuqest", %{conn: conn} do + public_tags_request = build(:public_tags_request) + + post_public_tasg_request_response = + conn + |> post( + "/api/account/v1/user/public_tags", + public_tags_request + ) + |> doc(description: "Submit request to add a public tag") + |> json_response(200) + + assert post_public_tasg_request_response["full_name"] == public_tags_request["full_name"] + assert post_public_tasg_request_response["email"] == public_tags_request["email"] + assert post_public_tasg_request_response["tags"] == public_tags_request["tags"] + assert post_public_tasg_request_response["website"] == public_tags_request["website"] + assert post_public_tasg_request_response["additional_comment"] == public_tags_request["additional_comment"] + assert post_public_tasg_request_response["addresses"] == public_tags_request["addresses"] + assert post_public_tasg_request_response["company"] == public_tags_request["company"] + assert post_public_tasg_request_response["is_owner"] == public_tags_request["is_owner"] + assert post_public_tasg_request_response["id"] + end + + test "get one public tags requests", %{conn: conn} do + public_tags_request = build(:public_tags_request) + + post_public_tasg_request_response = + conn + |> post( + "/api/account/v1/user/public_tags", + public_tags_request + ) + |> json_response(200) + + assert post_public_tasg_request_response["full_name"] == public_tags_request["full_name"] + assert post_public_tasg_request_response["email"] == public_tags_request["email"] + assert post_public_tasg_request_response["tags"] == public_tags_request["tags"] + assert post_public_tasg_request_response["website"] == public_tags_request["website"] + assert post_public_tasg_request_response["additional_comment"] == public_tags_request["additional_comment"] + assert post_public_tasg_request_response["addresses"] == public_tags_request["addresses"] + assert post_public_tasg_request_response["company"] == public_tags_request["company"] + assert post_public_tasg_request_response["is_owner"] == public_tags_request["is_owner"] + assert post_public_tasg_request_response["id"] + + assert conn + |> get("/api/account/v1/user/public_tags") + |> json_response(200) + |> Enum.map(&convert_date/1) == + [post_public_tasg_request_response] + |> Enum.map(&convert_date/1) + end + + test "get and delete several public tags requests", %{conn: conn} do + public_tags_list = build_list(10, :public_tags_request) + + final_list = + public_tags_list + |> Enum.map(fn request -> + response = + conn + |> post( + "/api/account/v1/user/public_tags", + request + ) + |> json_response(200) + + assert response["full_name"] == request["full_name"] + assert response["email"] == request["email"] + assert response["tags"] == request["tags"] + assert response["website"] == request["website"] + assert response["additional_comment"] == request["additional_comment"] + assert response["addresses"] == request["addresses"] + assert response["company"] == request["company"] + assert response["is_owner"] == request["is_owner"] + assert response["id"] + + convert_date(response) + end) + |> Enum.reverse() + + assert conn + |> get("/api/account/v1/user/public_tags") + |> doc(description: "Get list of requests to add a public tag") + |> json_response(200) + |> Enum.map(&convert_date/1) == final_list + + %{"id" => id} = Enum.at(final_list, 0) + + assert conn + |> delete("/api/account/v1/user/public_tags/#{id}", %{"remove_reason" => "reason"}) + |> doc(description: "Delete public tags request") + |> json_response(200) == %{"message" => "OK"} + + Enum.each(Enum.drop(final_list, 1), fn request -> + assert conn + |> delete("/api/account/v1/user/public_tags/#{request["id"]}", %{"remove_reason" => "reason"}) + |> json_response(200) == %{"message" => "OK"} + end) + + assert conn + |> get("/api/account/v1/user/public_tags") + |> json_response(200) == [] + end + + test "edit public tags request", %{conn: conn} do + public_tags_request = build(:public_tags_request) + + post_public_tasg_request_response = + conn + |> post( + "/api/account/v1/user/public_tags", + public_tags_request + ) + |> json_response(200) + + assert post_public_tasg_request_response["full_name"] == public_tags_request["full_name"] + assert post_public_tasg_request_response["email"] == public_tags_request["email"] + assert post_public_tasg_request_response["tags"] == public_tags_request["tags"] + assert post_public_tasg_request_response["website"] == public_tags_request["website"] + assert post_public_tasg_request_response["additional_comment"] == public_tags_request["additional_comment"] + assert post_public_tasg_request_response["addresses"] == public_tags_request["addresses"] + assert post_public_tasg_request_response["company"] == public_tags_request["company"] + assert post_public_tasg_request_response["is_owner"] == public_tags_request["is_owner"] + assert post_public_tasg_request_response["id"] + + assert conn + |> get("/api/account/v1/user/public_tags") + |> json_response(200) + |> Enum.map(&convert_date/1) == + [post_public_tasg_request_response] + |> Enum.map(&convert_date/1) + + new_public_tags_request = build(:public_tags_request) + + put_public_tasg_request_response = + conn + |> put( + "/api/account/v1/user/public_tags/#{post_public_tasg_request_response["id"]}", + new_public_tags_request + ) + |> doc(description: "Edit request to add a public tag") + |> json_response(200) + + assert put_public_tasg_request_response["full_name"] == new_public_tags_request["full_name"] + assert put_public_tasg_request_response["email"] == new_public_tags_request["email"] + assert put_public_tasg_request_response["tags"] == new_public_tags_request["tags"] + assert put_public_tasg_request_response["website"] == new_public_tags_request["website"] + assert put_public_tasg_request_response["additional_comment"] == new_public_tags_request["additional_comment"] + assert put_public_tasg_request_response["addresses"] == new_public_tags_request["addresses"] + assert put_public_tasg_request_response["company"] == new_public_tags_request["company"] + assert put_public_tasg_request_response["is_owner"] == new_public_tags_request["is_owner"] + assert put_public_tasg_request_response["id"] == post_public_tasg_request_response["id"] + + assert conn + |> get("/api/account/v1/user/public_tags") + |> json_response(200) + |> Enum.map(&convert_date/1) == + [put_public_tasg_request_response] + |> Enum.map(&convert_date/1) + end + end + + def convert_date(request) do + {:ok, time, _} = DateTime.from_iso8601(request["submission_date"]) + %{request | "submission_date" => Calendar.strftime(time, "%b %d, %Y")} + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/custom_abi_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/custom_abi_controller_test.exs new file mode 100644 index 000000000000..89ff276f69cb --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/custom_abi_controller_test.exs @@ -0,0 +1,186 @@ +defmodule BlockScoutWeb.Account.CustomABIControllerTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.Models.UserFromAuth + + @custom_abi "[{\"type\":\"function\",\"outputs\":[{\"type\":\"string\",\"name\":\"\"}],\"name\":\"name\",\"inputs\":[],\"constant\":true}]" + + setup %{conn: conn} do + auth = build(:auth) + + {:ok, user} = UserFromAuth.find_or_create(auth) + + {:ok, conn: Plug.Test.init_test_session(conn, current_user: user)} + end + + describe "test custom ABI functionality" do + test "custom ABI page opens correctly", %{conn: conn} do + result_conn = + conn + |> get(custom_abi_path(conn, :index)) + + assert html_response(result_conn, 200) =~ "Create a Custom ABI to interact with contracts." + end + + test "do not add custom ABI with wrong ABI", %{conn: conn} do + contract_address = insert(:address, contract_code: "0x0102") + + custom_abi = %{ + "name" => "1", + "address_hash" => to_string(contract_address), + "abi" => "" + } + + result_conn = + conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => custom_abi})) + + assert html_response(result_conn, 200) =~ "Add Custom ABI" + assert html_response(result_conn, 200) =~ to_string(contract_address.hash) + assert html_response(result_conn, 200) =~ "Required" + + result_conn_1 = + conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => Map.put(custom_abi, "abi", "123")})) + + assert html_response(result_conn_1, 200) =~ "Add Custom ABI" + assert html_response(result_conn_1, 200) =~ to_string(contract_address.hash) + assert html_response(result_conn_1, 200) =~ "Invalid format" + + result_conn_2 = + conn + |> get(custom_abi_path(conn, :index)) + + assert html_response(result_conn_2, 200) =~ "Create a Custom ABI to interact with contracts." + refute html_response(result_conn_2, 200) =~ to_string(contract_address.hash) + end + + test "add one custom abi and do not allow to create duplicates", %{conn: conn} do + contract_address = insert(:contract_address, contract_code: "0x0102") + + custom_abi = %{ + "name" => "1", + "address_hash" => to_string(contract_address), + "abi" => @custom_abi + } + + result_conn = + conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => custom_abi})) + + assert redirected_to(result_conn) == custom_abi_path(conn, :index) + + result_conn_2 = get(result_conn, custom_abi_path(conn, :index)) + assert html_response(result_conn_2, 200) =~ to_string(contract_address.hash) + assert html_response(result_conn_2, 200) =~ "Create a Custom ABI to interact with contracts." + + result_conn_1 = + conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => custom_abi})) + + assert html_response(result_conn_1, 200) =~ "Add Custom ABI" + assert html_response(result_conn_1, 200) =~ to_string(contract_address.hash) + assert html_response(result_conn_1, 200) =~ "Custom ABI for this address has already been added before" + end + + test "show error on address which is not smart contract", %{conn: conn} do + contract_address = insert(:address) + + custom_abi = %{ + "name" => "1", + "address_hash" => to_string(contract_address), + "abi" => @custom_abi + } + + result_conn = + conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => custom_abi})) + + assert html_response(result_conn, 200) =~ "Add Custom ABI" + assert html_response(result_conn, 200) =~ to_string(contract_address.hash) + assert html_response(result_conn, 200) =~ "Address is not a smart contract" + end + + test "user can add up to 15 custom ABIs", %{conn: conn} do + addresses = + Enum.map(1..15, fn _x -> + address = insert(:contract_address, contract_code: "0x0102") + + custom_abi = %{ + "name" => "1", + "address_hash" => to_string(address), + "abi" => @custom_abi + } + + assert conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => custom_abi})) + |> redirected_to() == custom_abi_path(conn, :index) + + to_string(address.hash) + end) + + assert abi_list = + conn + |> get(custom_abi_path(conn, :index)) + |> html_response(200) + + Enum.each(addresses, fn address -> assert abi_list =~ address end) + + address = insert(:contract_address, contract_code: "0x0102") + + custom_abi = %{ + "name" => "1", + "address_hash" => to_string(address), + "abi" => @custom_abi + } + + assert error_form = + conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => custom_abi})) + |> html_response(200) + + assert error_form =~ "Add Custom ABI" + assert error_form =~ "Max 15 ABIs per account" + assert error_form =~ to_string(address.hash) + + assert abi_list_new = + conn + |> get(custom_abi_path(conn, :index)) + |> html_response(200) + + Enum.each(addresses, fn address -> assert abi_list_new =~ address end) + + refute abi_list_new =~ to_string(address.hash) + assert abi_list_new =~ "You can create up to 15 Custom ABIs per account." + end + + test "after adding custom ABI on address page appear Read/Write Contract tab", %{conn: conn} do + contract_address = insert(:contract_address, contract_code: "0x0102") + + custom_abi = %{ + "name" => "1", + "address_hash" => to_string(contract_address), + "abi" => + "[{\"type\":\"function\",\"outputs\":[{\"type\":\"string\",\"name\":\"\"}],\"name\":\"name\",\"inputs\":[],\"constant\":true},{\"type\":\"function\",\"outputs\":[{\"type\":\"bool\",\"name\":\"success\"}],\"name\":\"approve\",\"inputs\":[{\"type\":\"address\",\"name\":\"_spender\"},{\"type\":\"uint256\",\"name\":\"_value\"}],\"constant\":false}]" + } + + result_conn = + conn + |> post(custom_abi_path(conn, :create, %{"custom_abi" => custom_abi})) + + assert redirected_to(result_conn) == custom_abi_path(conn, :index) + + result_conn_2 = get(result_conn, custom_abi_path(conn, :index)) + assert html_response(result_conn_2, 200) =~ to_string(contract_address.hash) + assert html_response(result_conn_2, 200) =~ "Create a Custom ABI to interact with contracts." + + assert contract_page = + result_conn + |> get(address_contract_path(result_conn, :index, to_string(contract_address))) + |> html_response(200) + + assert contract_page =~ "Write Contract" + assert contract_page =~ "Read Contract" + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs index a78aff76af6e..5301e0b1c54b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs @@ -328,6 +328,157 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do end) end + test "next page doesn't miss internal transactions", %{conn: conn} do + address = insert(:address) + + a_block = insert(:block, number: 1000) + b_block = insert(:block, number: 2000) + + transaction_1 = + :transaction + |> insert() + |> with_block(a_block) + + transaction_2 = + :transaction + |> insert() + |> with_block(a_block) + + transaction_3 = + :transaction + |> insert() + |> with_block(b_block) + + from_internal_transactions = + 1..55 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction_1, + from_address: address, + index: index, + block_number: transaction_1.block_number, + transaction_index: transaction_1.index, + block_hash: a_block.hash, + block_index: index + ) + end) + + to_internal_transactions = + 1..55 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction_2, + to_address: address, + index: index, + block_number: transaction_2.block_number, + transaction_index: transaction_2.index, + block_hash: a_block.hash, + block_index: 55 + index + ) + end) + + created_contract_internal_transactions = + 1..55 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction_3, + created_contract_address: address, + index: index, + block_number: transaction_3.block_number, + transaction_index: transaction_3.index, + block_hash: b_block.hash, + block_index: index + ) + end) + + {second_page_contract_items, first_page_items} = Enum.split(created_contract_internal_transactions, 5) + {third_page_to_items, second_page_to_items} = Enum.split(to_internal_transactions, 10) + {fourth_page_items, third_page_from_items} = Enum.split(from_internal_transactions, 15) + + second_page_items = second_page_contract_items ++ second_page_to_items + third_page_items = third_page_to_items ++ third_page_from_items + + path = address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)) + + first_page_response = + conn + |> get(path, %{"type" => "JSON"}) + |> json_response(200) + |> Map.get("items") + + second_page_response = + conn + |> get(path, %{ + "block_number" => Integer.to_string(b_block.number), + "transaction_index" => Integer.to_string(transaction_3.index), + "index" => "6", + "type" => "JSON" + }) + |> json_response(200) + |> Map.get("items") + + third_page_response = + conn + |> get(path, %{ + "block_number" => Integer.to_string(a_block.number), + "transaction_index" => Integer.to_string(transaction_2.index), + "index" => "11", + "type" => "JSON" + }) + |> json_response(200) + |> Map.get("items") + + fourth_page_response = + conn + |> get(path, %{ + "block_number" => Integer.to_string(a_block.number), + "transaction_index" => Integer.to_string(transaction_1.index), + "index" => "16", + "type" => "JSON" + }) + |> json_response(200) + |> Map.get("items") + + assert Enum.count(first_page_response) == 50 + + assert Enum.all?(first_page_items, fn internal_transaction -> + Enum.any?(first_page_response, fn tile -> + String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") + end) + end) + + assert Enum.count(second_page_response) == 50 + + assert Enum.all?(second_page_items, fn internal_transaction -> + Enum.any?(second_page_response, fn tile -> + String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") + end) + end) + + assert Enum.count(third_page_response) == 50 + + assert Enum.all?(third_page_items, fn internal_transaction -> + Enum.any?(third_page_response, fn tile -> + String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") + end) + end) + + assert Enum.count(fourth_page_response) == 15 + + assert Enum.all?(fourth_page_items, fn internal_transaction -> + Enum.any?(fourth_page_response, fn tile -> + String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") + end) + end) + end + test "next_page_params exist if not on last page", %{conn: conn} do address = insert(:address) block = %Block{number: number} = insert(:block, number: 7000) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index e28e6ac4013b..159c1be26179 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -8,6 +8,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do alias Explorer.Chain.{Address, Transaction} alias Explorer.ExchangeRates.Token + setup :verify_on_exit! + describe "GET index/2" do setup :set_mox_global @@ -159,7 +161,10 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end describe "GET token-transfers-csv/2" do - test "exports token transfers to csv", %{conn: conn} do + test "do not export token transfers to csv without recaptcha recaptcha_response provided", %{conn: conn} do + BlockScoutWeb.TestCaptchaHelper + |> expect(:recaptcha_passed?, fn _captcha_response -> false end) + address = insert(:address) transaction = @@ -180,12 +185,71 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do "to_period" => to_period }) + assert conn.status == 404 + end + + test "do not export token transfers to csv without recaptcha passed", %{conn: conn} do + BlockScoutWeb.TestCaptchaHelper + |> expect(:recaptcha_passed?, fn _captcha_response -> false end) + + address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:token_transfer, transaction: transaction, from_address: address, block_number: transaction.block_number) + insert(:token_transfer, transaction: transaction, to_address: address, block_number: transaction.block_number) + + from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) + to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) + + conn = + get(conn, "/token-transfers-csv", %{ + "address_id" => Address.checksum(address.hash), + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => "123" + }) + + assert conn.status == 404 + end + + test "exports token transfers to csv", %{conn: conn} do + BlockScoutWeb.TestCaptchaHelper + |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + + address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:token_transfer, transaction: transaction, from_address: address, block_number: transaction.block_number) + insert(:token_transfer, transaction: transaction, to_address: address, block_number: transaction.block_number) + + from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) + to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) + + conn = + get(conn, "/token-transfers-csv", %{ + "address_id" => Address.checksum(address.hash), + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => "123" + }) + assert conn.resp_body |> String.split("\n") |> Enum.count() == 4 end end describe "GET transactions_csv/2" do test "download csv file with transactions", %{conn: conn} do + BlockScoutWeb.TestCaptchaHelper + |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + address = insert(:address) :transaction @@ -203,7 +267,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do get(conn, "/transactions-csv", %{ "address_id" => Address.checksum(address.hash), "from_period" => from_period, - "to_period" => to_period + "to_period" => to_period, + "recaptcha_response" => "123" }) assert conn.resp_body |> String.split("\n") |> Enum.count() == 4 @@ -212,6 +277,9 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do describe "GET internal_transactions_csv/2" do test "download csv file with internal transactions", %{conn: conn} do + BlockScoutWeb.TestCaptchaHelper + |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + address = insert(:address) transaction_1 = @@ -266,7 +334,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do get(conn, "/internal-transactions-csv", %{ "address_id" => Address.checksum(address.hash), "from_period" => from_period, - "to_period" => to_period + "to_period" => to_period, + "recaptcha_response" => "123" }) assert conn.resp_body |> String.split("\n") |> Enum.count() == 5 @@ -275,6 +344,9 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do describe "GET logs_csv/2" do test "download csv file with logs", %{conn: conn} do + BlockScoutWeb.TestCaptchaHelper + |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + address = insert(:address) transaction_1 = @@ -323,7 +395,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do get(conn, "/logs-csv", %{ "address_id" => Address.checksum(address.hash), "from_period" => from_period, - "to_period" => to_period + "to_period" => to_period, + "recaptcha_response" => "123" }) assert conn.resp_body |> String.split("\n") |> Enum.count() == 5 diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 38d614bdd469..2f3ef1038c2e 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -82,6 +82,49 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do ] = response["result"] end + test "sort by hash", %{params: params, conn: conn} do + inserted_at = Timex.shift(Timex.now(), minutes: -10) + + first_address = + insert(:address, + hash: "0x0000000000000000000000000000000000000001", + fetched_coin_balance: 10, + inserted_at: inserted_at + ) + + second_address = + insert(:address, + hash: "0x0000000000000000000000000000000000000002", + fetched_coin_balance: 100, + inserted_at: inserted_at + ) + + first_address_hash = to_string(first_address.hash) + second_address_hash = to_string(second_address.hash) + + first_address_inserted_at = to_string(first_address.inserted_at) + second_address_inserted_at = to_string(second_address.inserted_at) + + response = + conn + |> get("/api", params) + |> json_response(200) + + schema = listaccounts_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["message"] == "OK" + assert response["status"] == "1" + + assert [ + %{ + "address" => ^first_address_hash + }, + %{ + "address" => ^second_address_hash + } + ] = response["result"] + end + test "with a stale balance", %{conn: conn, params: params} do now = Timex.now() @@ -1047,7 +1090,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with startblock and endblock params", %{conn: conn} do + test "with start_block and end_block params", %{conn: conn} do blocks = [_, second_block, third_block, _] = insert_list(4, :block) address = insert(:address) @@ -1061,8 +1104,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "startblock" => "#{second_block.number}", - "endblock" => "#{third_block.number}" + "start_block" => "#{second_block.number}", + "end_block" => "#{third_block.number}" } expected_block_numbers = [ @@ -1086,7 +1129,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with startblock but without endblock", %{conn: conn} do + test "with start_block but without end_block", %{conn: conn} do blocks = [_, _, third_block, fourth_block] = insert_list(4, :block) address = insert(:address) @@ -1100,7 +1143,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "startblock" => "#{third_block.number}" + "start_block" => "#{third_block.number}" } expected_block_numbers = [ @@ -1124,7 +1167,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with endblock but without startblock", %{conn: conn} do + test "with end_block but without start_block", %{conn: conn} do blocks = [first_block, second_block, _, _] = insert_list(4, :block) address = insert(:address) @@ -1138,7 +1181,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "endblock" => "#{second_block.number}" + "end_block" => "#{second_block.number}" } expected_block_numbers = [ @@ -1162,7 +1205,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "ignores invalid startblock and endblock", %{conn: conn} do + test "ignores invalid start_block and end_block", %{conn: conn} do blocks = [_, _, _, _] = insert_list(4, :block) address = insert(:address) @@ -1176,8 +1219,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "startblock" => "invalidstart", - "endblock" => "invalidend" + "start_block" => "invalidstart", + "end_block" => "invalidend" } assert response = @@ -1191,7 +1234,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with starttimestamp and endtimestamp params", %{conn: conn} do + test "with start_timestamp and end_timestamp params", %{conn: conn} do now = Timex.now() timestamp1 = Timex.shift(now, hours: -6) timestamp2 = Timex.shift(now, hours: -3) @@ -1214,8 +1257,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "starttimestamp" => "#{start_timestamp}", - "endtimestamp" => "#{end_timestamp}" + "start_timestamp" => "#{start_timestamp}", + "end_timestamp" => "#{end_timestamp}" } expected_block_numbers = [ @@ -1239,7 +1282,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with starttimestamp but without endtimestamp", %{conn: conn} do + test "with start_timestamp but without end_timestamp", %{conn: conn} do now = Timex.now() timestamp1 = Timex.shift(now, hours: -6) timestamp2 = Timex.shift(now, hours: -3) @@ -1261,7 +1304,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "starttimestamp" => "#{start_timestamp}" + "start_timestamp" => "#{start_timestamp}" } expected_block_numbers = [ @@ -1287,7 +1330,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with endtimestamp but without starttimestamp", %{conn: conn} do + test "with end_timestamp but without start_timestamp", %{conn: conn} do now = Timex.now() timestamp1 = Timex.shift(now, hours: -6) timestamp2 = Timex.shift(now, hours: -3) @@ -1309,7 +1352,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "endtimestamp" => "#{end_timestamp}" + "end_timestamp" => "#{end_timestamp}" } expected_block_numbers = [ @@ -1333,7 +1376,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with filterby=to option", %{conn: conn} do + test "with filter_by=to option", %{conn: conn} do block = insert(:block) address = insert(:address) @@ -1347,7 +1390,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "filterby" => "to" + "filter_by" => "to" } assert response = @@ -1361,7 +1404,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with filterby=from option", %{conn: conn} do + test "with filter_by=from option", %{conn: conn} do block = insert(:block) address = insert(:address) @@ -1378,7 +1421,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "filterby" => "from" + "filter_by" => "from" } assert response = @@ -1842,6 +1885,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "contractAddress" => "#{contract_address.hash}", "input" => "", "type" => "#{internal_transaction.type}", + "callType" => "#{internal_transaction.call_type}", "gas" => "#{internal_transaction.gas}", "gasUsed" => "#{internal_transaction.gas_used}", "index" => "#{internal_transaction.index}", @@ -2009,6 +2053,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "contractAddress" => "#{contract_address.hash}", "input" => "", "type" => "#{internal_transaction.type}", + "callType" => "#{internal_transaction.call_type}", "gas" => "#{internal_transaction.gas}", "gasUsed" => "#{internal_transaction.gas_used}", "isError" => "0", @@ -2176,7 +2221,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do token_transfer = insert(:token_transfer, %{ token_contract_address: token_address, - token_id: 666, + token_ids: [666], transaction: transaction, block: transaction.block, block_number: transaction.block_number @@ -2196,7 +2241,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do |> get("/api", params) |> json_response(200) - assert result["tokenID"] == to_string(token_transfer.token_id) + assert result["tokenID"] == to_string(List.first(token_transfer.token_ids)) assert response["status"] == "1" assert response["message"] == "OK" assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) @@ -2785,16 +2830,16 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do describe "optional_params/1" do test "includes valid optional params in the required format" do params = %{ - "startblock" => "100", - "endblock" => "120", + "start_block" => "100", + "end_block" => "120", "sort" => "asc", # page number "page" => "1", # page size "offset" => "2", - "filterby" => "to", - "starttimestamp" => "1539186474", - "endtimestamp" => "1539186474" + "filter_by" => "to", + "start_timestamp" => "1539186474", + "end_timestamp" => "1539186474" } optional_params = AddressController.optional_params(params) @@ -2830,20 +2875,20 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert AddressController.optional_params(params3) == %{} end - test "'filterby' value can be 'to' or 'from'" do - params1 = %{"filterby" => "to"} + test "'filter_by' value can be 'to' or 'from'" do + params1 = %{"filter_by" => "to"} optional_params1 = AddressController.optional_params(params1) assert optional_params1.filter_by == "to" - params2 = %{"filterby" => "from"} + params2 = %{"filter_by" => "from"} optional_params2 = AddressController.optional_params(params2) assert optional_params2.filter_by == "from" - params3 = %{"filterby" => "invalid"} + params3 = %{"filter_by" => "invalid"} assert AddressController.optional_params(params3) == %{} end @@ -2854,25 +2899,25 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do test "ignores invalid optional params, keeps valid ones" do params1 = %{ - "startblock" => "invalid", - "endblock" => "invalid", + "start_block" => "invalid", + "end_block" => "invalid", "sort" => "invalid", "page" => "invalid", "offset" => "invalid", - "starttimestamp" => "invalid", - "endtimestamp" => "invalid" + "start_timestamp" => "invalid", + "end_timestamp" => "invalid" } assert AddressController.optional_params(params1) == %{} params2 = %{ - "startblock" => "4", - "endblock" => "10", + "start_block" => "4", + "end_block" => "10", "sort" => "invalid", "page" => "invalid", "offset" => "invalid", - "starttimestamp" => "invalid", - "endtimestamp" => "invalid" + "start_timestamp" => "invalid", + "end_timestamp" => "invalid" } optional_params = AddressController.optional_params(params2) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index 4f56efb7f00d..5c4306b232ee 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -6,6 +6,66 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do import Mox + def prepare_contracts do + insert(:contract_address) + {:ok, dt_1, _} = DateTime.from_iso8601("2022-09-20 10:00:00Z") + + contract_1 = + insert(:smart_contract, + contract_code_md5: "123", + name: "Test 1", + optimization: "1", + compiler_version: "v0.6.8+commit.0bbfe453", + abi: [%{foo: "bar"}], + inserted_at: dt_1 + ) + + insert(:contract_address) + {:ok, dt_2, _} = DateTime.from_iso8601("2022-09-22 10:00:00Z") + + contract_2 = + insert(:smart_contract, + contract_code_md5: "12345", + name: "Test 2", + optimization: "0", + compiler_version: "v0.7.5+commit.eb77ed08", + abi: [%{foo: "bar-2"}], + inserted_at: dt_2 + ) + + insert(:contract_address) + {:ok, dt_3, _} = DateTime.from_iso8601("2022-09-24 10:00:00Z") + + contract_3 = + insert(:smart_contract, + contract_code_md5: "1234567", + name: "Test 3", + optimization: "1", + compiler_version: "v0.4.26+commit.4563c3fc", + abi: [%{foo: "bar-3"}], + inserted_at: dt_3 + ) + + [contract_1, contract_2, contract_3] + end + + def result(contract) do + %{ + "ABI" => Jason.encode!(contract.abi), + "Address" => to_string(contract.address_hash), + "CompilerVersion" => contract.compiler_version, + "ContractName" => contract.name, + "OptimizationUsed" => if(contract.optimization, do: "1", else: "0") + } + end + + defp result_not_verified(address_hash) do + %{ + "ABI" => "Contract source code not verified", + "Address" => to_string(address_hash) + } + end + describe "listcontracts" do setup do %{params: %{"module" => "contract", "action" => "listcontracts"}} @@ -47,15 +107,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => Jason.encode!(contract.abi), - "Address" => to_string(contract.address_hash), - "CompilerVersion" => contract.compiler_version, - "ContractName" => contract.name, - "OptimizationUsed" => if(contract.optimization, do: "1", else: "0") - } - ] + assert response["result"] == [result(contract)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -71,12 +123,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash) - } - ] + assert response["result"] == [result_not_verified(address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -93,12 +140,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash) - } - ] + assert response["result"] == [result_not_verified(address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -119,12 +161,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash) - } - ] + assert response["result"] == [result_not_verified(address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -141,15 +178,82 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => Jason.encode!(contract.abi), - "Address" => to_string(contract.address_hash), - "CompilerVersion" => contract.compiler_version, - "ContractName" => contract.name, - "OptimizationUsed" => if(contract.optimization, do: "1", else: "0") - } - ] + assert response["result"] == [result(contract)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only verified contracts in the date range shows only verified contracts in that range", %{ + params: params, + conn: conn + } do + [contract_1, contract_2, contract_3] = prepare_contracts() + + filter_params = + params + |> Map.put("filter", "verified") + |> Map.put("verified_at_start_timestamp", "1663749418") + |> Map.put("verified_at_end_timestamp", "1663922218") + + response = + conn + |> get("/api", filter_params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result(contract_2)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only verified contracts with start created_at timestamp >= given timestamp shows only verified contracts in that range", + %{ + params: params, + conn: conn + } do + [contract_1, contract_2, contract_3] = prepare_contracts() + + filter_params = + params + |> Map.put("filter", "verified") + |> Map.put("verified_at_start_timestamp", "1663749418") + + response = + conn + |> get("/api", filter_params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result(contract_2), result(contract_3)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only verified contracts with end created_at timestamp < given timestamp shows only verified contracts in that range", + %{ + params: params, + conn: conn + } do + [contract_1, contract_2, contract_3] = prepare_contracts() + + filter_params = + params + |> Map.put("filter", "verified") + |> Map.put("verified_at_end_timestamp", "1663922218") + + response = + conn + |> get("/api", filter_params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result(contract_1), result(contract_2)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -166,12 +270,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(decompiled_smart_contract.address_hash) - } - ] + assert response["result"] == [result_not_verified(decompiled_smart_contract.address_hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -188,12 +287,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(smart_contract.address_hash) - } - ] + assert response["result"] == [result_not_verified(smart_contract.address_hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -212,10 +306,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(smart_contract.address_hash) - } in response["result"] + assert result_not_verified(smart_contract.address_hash) in response["result"] refute to_string(non_match.address_hash) in Enum.map(response["result"], &Map.get(&1, "Address")) assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) @@ -234,12 +325,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(contract_address.hash) - } - ] + assert response["result"] == [result_not_verified(contract_address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -261,12 +347,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(contract_address.hash) - } - ] + assert response["result"] == [result_not_verified(contract_address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -396,20 +477,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do expected_result = [ %{ - "Address" => "", - "SourceCode" => "", - "ABI" => "Contract source code not verified", - "ContractName" => "", - "CompilerVersion" => "", - "OptimizationUsed" => "", - "DecompiledSourceCode" => "", - "DecompilerVersion" => "", - "ConstructorArguments" => "", - "EVMVersion" => "", - "ExternalLibraries" => "", - "OptimizationRuns" => "", - "FileName" => "", - "IsProxy" => "false" + "Address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" } ] diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs new file mode 100644 index 000000000000..33fbe54aee77 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -0,0 +1,1207 @@ +defmodule BlockScoutWeb.API.V2.AddressControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.{Chain, Repo} + + alias Explorer.Chain.{ + Address, + Address.CoinBalance, + Block, + InternalTransaction, + Log, + Token, + TokenTransfer, + Transaction + } + + alias Explorer.Chain.Address.CurrentTokenBalance + + describe "/addresses/{address_hash}" do + test "get 404 on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get address & get the same response for checksummed and downcased parameter", %{conn: conn} do + address = insert(:address) + + correct_reponse = %{ + "hash" => Address.checksum(address.hash), + "implementation_name" => nil, + "is_contract" => false, + "is_verified" => false, + "name" => nil, + "private_tags" => [], + "public_tags" => [], + "watchlist_names" => [], + "creator_address_hash" => nil, + "creation_tx_hash" => nil, + "token" => nil, + "coin_balance" => nil, + "exchange_rate" => nil, + "implementation_name" => nil, + "implementation_address" => nil, + "block_number_balance_updated_at" => nil + } + + request = get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}") + assert ^correct_reponse = json_response(request, 200) + + request = get(conn, "/api/v2/addresses/#{String.downcase(to_string(address.hash))}") + assert ^correct_reponse = json_response(request, 200) + end + end + + describe "/addresses/{address_hash}/counters" do + test "get 404 on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/counters") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/counters") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get counters with 0s", %{conn: conn} do + address = insert(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/counters") + + assert %{ + "transactions_count" => "0", + "token_transfers_count" => "0", + "gas_usage_count" => "0", + "validations_count" => "0" + } = json_response(request, 200) + end + + test "get counters", %{conn: conn} do + address = insert(:address) + + tx_from = insert(:transaction, from_address: address) |> with_block() + insert(:transaction, to_address: address) |> with_block() + another_tx = insert(:transaction) |> with_block() + + insert(:token_transfer, + from_address: address, + transaction: another_tx, + block: another_tx.block, + block_number: another_tx.block_number + ) + + insert(:token_transfer, + to_address: address, + transaction: another_tx, + block: another_tx.block, + block_number: another_tx.block_number + ) + + insert(:block, miner: address) + + Chain.transaction_count(address) + Chain.token_transfers_count(address) + Chain.gas_usage_count(address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/counters") + + gas_used = to_string(tx_from.gas_used) + + assert %{ + "transactions_count" => "2", + "token_transfers_count" => "2", + "gas_usage_count" => ^gas_used, + "validations_count" => "1" + } = json_response(request, 200) + end + end + + describe "/addresses/{address_hash}/transactions" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/transactions") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get relevant transaction", %{conn: conn} do + address = insert(:address) + + tx = insert(:transaction, from_address: address) |> with_block() + + insert(:transaction) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(tx, Enum.at(response["items"], 0)) + end + + test "get pending transaction", %{conn: conn} do + address = insert(:address) + + tx = insert(:transaction, from_address: address) |> with_block() + pending_tx = insert(:transaction, from_address: address) + + insert(:transaction) |> with_block() + insert(:transaction) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 2 + assert response["next_page_params"] == nil + compare_item(pending_tx, Enum.at(response["items"], 0)) + compare_item(tx, Enum.at(response["items"], 1)) + end + + test "get only :to transaction", %{conn: conn} do + address = insert(:address) + + insert(:transaction, from_address: address) |> with_block() + tx = insert(:transaction, to_address: address) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"filter" => "to"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(tx, Enum.at(response["items"], 0)) + end + + test "get only :from transactions", %{conn: conn} do + address = insert(:address) + + tx = insert(:transaction, from_address: address) |> with_block() + insert(:transaction, to_address: address) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"filter" => "from"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(tx, Enum.at(response["items"], 0)) + end + + test "validated txs can paginate", %{conn: conn} do + address = insert(:address) + + txs = insert_list(51, :transaction, from_address: address) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test "pending txs can paginate", %{conn: conn} do + address = insert(:address) + + txs = insert_list(51, :transaction, from_address: address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test "pending + validated txs can paginate", %{conn: conn} do + address = insert(:address) + + txs_pending = insert_list(51, :transaction, from_address: address) + txs_validated = insert_list(50, :transaction, to_address: address) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs_pending, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs_pending, 1), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 50 + assert response_2nd_page["next_page_params"] != nil + compare_item(Enum.at(txs_pending, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(txs_validated, 49), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(txs_validated, 1), Enum.at(response_2nd_page["items"], 49)) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response_2nd_page["next_page_params"]) + assert response = json_response(request, 200) + + check_paginated_response(response_2nd_page, response, txs_validated ++ [Enum.at(txs_pending, 0)]) + end + + test ":to txs can paginate", %{conn: conn} do + address = insert(:address) + + txs = insert_list(51, :transaction, to_address: address) |> with_block() + insert_list(51, :transaction, from_address: address) |> with_block() + + filter = %{"filter" => "to"} + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/transactions", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test ":from txs can paginate", %{conn: conn} do + address = insert(:address) + + insert_list(51, :transaction, to_address: address) |> with_block() + txs = insert_list(51, :transaction, from_address: address) |> with_block() + + filter = %{"filter" => "from"} + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/transactions", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test ":from + :to txs can paginate", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(50, :transaction, from_address: address) |> with_block() + txs_to = insert_list(51, :transaction, to_address: address) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs_to, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs_to, 1), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 50 + assert response_2nd_page["next_page_params"] != nil + compare_item(Enum.at(txs_to, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(txs_from, 49), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(txs_from, 1), Enum.at(response_2nd_page["items"], 49)) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response_2nd_page["next_page_params"]) + assert response = json_response(request, 200) + + check_paginated_response(response_2nd_page, response, txs_from ++ [Enum.at(txs_to, 0)]) + end + end + + describe "/addresses/{address_hash}/token-transfers" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/token-transfers") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get relevant token transfer", %{conn: conn} do + address = insert(:address) + + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + + token_transfer = + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(token_transfer, Enum.at(response["items"], 0)) + end + + test "get only :to token transfer", %{conn: conn} do + address = insert(:address) + + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + + token_transfer = + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", %{"filter" => "to"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(token_transfer, Enum.at(response["items"], 0)) + end + + test "get only :from token transfer", %{conn: conn} do + address = insert(:address) + + tx = insert(:transaction) |> with_block() + + token_transfer = + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", %{"filter" => "from"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(token_transfer, Enum.at(response["items"], 0)) + end + + test "token transfers can paginate", %{conn: conn} do + address = insert(:address) + + token_tranfers = + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + end + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_tranfers) + end + + test ":to token transfers can paginate", %{conn: conn} do + address = insert(:address) + + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + end + + token_tranfers = + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + end + + filter = %{"filter" => "to"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_tranfers) + end + + test ":from token transfers can paginate", %{conn: conn} do + address = insert(:address) + + token_tranfers = + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + end + + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + end + + filter = %{"filter" => "from"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_tranfers) + end + + test ":from + :to tt can paginate", %{conn: conn} do + address = insert(:address) + + tt_from = + for _ <- 0..49 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + end + + tt_to = + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + end + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(tt_to, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(tt_to, 1), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 50 + assert response_2nd_page["next_page_params"] != nil + compare_item(Enum.at(tt_to, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(tt_from, 49), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(tt_from, 1), Enum.at(response_2nd_page["items"], 49)) + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", response_2nd_page["next_page_params"]) + assert response = json_response(request, 200) + + check_paginated_response(response_2nd_page, response, tt_from ++ [Enum.at(tt_to, 0)]) + end + + test "check token type filters", %{conn: conn} do + address = insert(:address) + + erc_20_token = insert(:token, type: "ERC-20") + + erc_20_tt = + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + from_address: address, + token_contract_address: erc_20_token.contract_address + ) + end + + erc_721_token = insert(:token, type: "ERC-721") + + erc_721_tt = + for x <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + from_address: address, + token_contract_address: erc_721_token.contract_address, + token_ids: [x] + ) + end + + erc_1155_token = insert(:token, type: "ERC-1155") + + erc_1155_tt = + for x <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + from_address: address, + token_contract_address: erc_1155_token.contract_address, + token_ids: [x] + ) + end + + # -- ERC-20 -- + filter = %{"type" => "ERC-20"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_20_tt) + # -- ------ -- + + # -- ERC-721 -- + filter = %{"type" => "ERC-721"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_721_tt) + # -- ------ -- + + # -- ERC-1155 -- + filter = %{"type" => "ERC-1155"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_1155_tt) + # -- ------ -- + + # two filters simultaneously + filter = %{"type" => "ERC-1155,ERC-20"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(erc_1155_tt, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(erc_1155_tt, 1), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 50 + assert response_2nd_page["next_page_params"] != nil + compare_item(Enum.at(erc_1155_tt, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(erc_20_tt, 50), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(erc_20_tt, 2), Enum.at(response_2nd_page["items"], 49)) + + request_3rd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/token-transfers", + Map.merge(response_2nd_page["next_page_params"], filter) + ) + + assert response_3rd_page = json_response(request_3rd_page, 200) + assert Enum.count(response_3rd_page["items"]) == 2 + assert response_3rd_page["next_page_params"] == nil + compare_item(Enum.at(erc_20_tt, 1), Enum.at(response_3rd_page["items"], 0)) + compare_item(Enum.at(erc_20_tt, 0), Enum.at(response_3rd_page["items"], 1)) + # -- ------ -- + end + + test "type and direction filters at the same time", %{conn: conn} do + address = insert(:address) + + erc_20_token = insert(:token, type: "ERC-20") + + erc_20_tt = + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + from_address: address, + token_contract_address: erc_20_token.contract_address + ) + end + + erc_721_token = insert(:token, type: "ERC-721") + + erc_721_tt = + for x <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + to_address: address, + token_contract_address: erc_721_token.contract_address, + token_ids: [x] + ) + end + + filter = %{"type" => "ERC-721", "filter" => "from"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + + filter = %{"type" => "ERC-721", "filter" => "to"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_721_tt) + + filter = %{"type" => "ERC-721,ERC-20", "filter" => "to"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_721_tt) + + filter = %{"type" => "ERC-721,ERC-20", "filter" => "from"} + request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_20_tt) + end + end + + describe "/addresses/{address_hash}/internal-transactions" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/internal-transactions") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get internal tx and filter working", %{conn: conn} do + address = insert(:address) + + tx = + :transaction + |> insert() + |> with_block() + + internal_tx_from = + insert(:internal_transaction, + transaction: tx, + index: 1, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: 1, + from_address: address + ) + + internal_tx_to = + insert(:internal_transaction, + transaction: tx, + index: 2, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: 2, + to_address: address + ) + + request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 2 + assert response["next_page_params"] == nil + + compare_item(internal_tx_from, Enum.at(response["items"], 1)) + compare_item(internal_tx_to, Enum.at(response["items"], 0)) + + request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", %{"filter" => "from"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(internal_tx_from, Enum.at(response["items"], 0)) + + request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", %{"filter" => "to"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(internal_tx_to, Enum.at(response["items"], 0)) + end + + test "internal txs can paginate", %{conn: conn} do + address = insert(:address) + + tx = + :transaction + |> insert() + |> with_block() + + itxs_from = + for i <- 1..51 do + insert(:internal_transaction, + transaction: tx, + index: i, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: i, + from_address: address + ) + end + + request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions") + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", response["next_page_params"]) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, itxs_from) + + itxs_to = + for i <- 52..102 do + insert(:internal_transaction, + transaction: tx, + index: i, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: i, + to_address: address + ) + end + + filter = %{"filter" => "to"} + request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/internal-transactions", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, itxs_to) + + filter = %{"filter" => "from"} + request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/internal-transactions", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, itxs_from) + end + end + + describe "/addresses/{address_hash}/blocks-validated" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/blocks-validated") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/blocks-validated") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get relevant block validated", %{conn: conn} do + address = insert(:address) + insert(:block) + block = insert(:block, miner: address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/blocks-validated") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + compare_item(block, Enum.at(response["items"], 0)) + end + + test "blocks validated can be paginated", %{conn: conn} do + address = insert(:address) + insert(:block) + blocks = insert_list(51, :block, miner: address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/blocks-validated") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/blocks-validated", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, blocks) + end + end + + describe "/addresses/{address_hash}/token-balances" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-balances") + + assert response = json_response(request, 200) + assert response == [] + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/token-balances") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get token balance", %{conn: conn} do + address = insert(:address) + + ctbs = + for _ <- 0..50 do + insert(:address_current_token_balance_with_token_id, address: address) |> Repo.preload([:token]) + end + |> Enum.sort_by(fn x -> x.value end, :desc) + + request = get(conn, "/api/v2/addresses/#{address.hash}/token-balances") + + assert response = json_response(request, 200) + + for i <- 0..50 do + compare_item(Enum.at(ctbs, i), Enum.at(response, i)) + end + end + end + + describe "/addresses/{address_hash}/coin-balance-history" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/coin-balance-history") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/coin-balance-history") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get coin balance history", %{conn: conn} do + address = insert(:address) + + insert(:address_coin_balance) + acb = insert(:address_coin_balance, address: address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/coin-balance-history") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + compare_item(acb, Enum.at(response["items"], 0)) + end + + test "coin balance history can paginate", %{conn: conn} do + address = insert(:address) + + acbs = insert_list(51, :address_coin_balance, address: address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/coin-balance-history") + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/addresses/#{address.hash}/coin-balance-history", response["next_page_params"]) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, acbs) + end + end + + describe "/addresses/{address_hash}/coin-balance-history-by-day" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/coin-balance-history-by-day") + + assert response = json_response(request, 200) + assert response == [] + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/coin-balance-history-by-day") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get coin balance history by day", %{conn: conn} do + address = insert(:address) + noon = Timex.now() |> Timex.beginning_of_day() |> Timex.set(hour: 12) + block = insert(:block, timestamp: noon, number: 2) + block_one_day_ago = insert(:block, timestamp: Timex.shift(noon, days: -1), number: 1) + insert(:fetched_balance, address_hash: address.hash, value: 1000, block_number: block.number) + insert(:fetched_balance, address_hash: address.hash, value: 2000, block_number: block_one_day_ago.number) + insert(:fetched_balance_daily, address_hash: address.hash, value: 1000, day: noon) + insert(:fetched_balance_daily, address_hash: address.hash, value: 2000, day: Timex.shift(noon, days: -1)) + + request = get(conn, "/api/v2/addresses/#{address.hash}/coin-balance-history-by-day") + + response = json_response(request, 200) + + assert [ + %{"date" => _, "value" => "2000"}, + %{"date" => _, "value" => "1000"}, + %{"date" => _, "value" => "1000"} + ] = response + end + end + + describe "/addresses/{address_hash}/logs" do + test "get empty list on non existing address", %{conn: conn} do + address = build(:address) + + request = get(conn, "/api/v2/addresses/#{address.hash}/logs") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/addresses/0x/logs") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get log", %{conn: conn} do + address = insert(:address) + + tx = + :transaction + |> insert() + |> with_block() + + log = + insert(:log, + transaction: tx, + index: 1, + block: tx.block, + block_number: tx.block_number, + address: address + ) + + request = get(conn, "/api/v2/addresses/#{address.hash}/logs") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(log, Enum.at(response["items"], 0)) + end + + # for some reasons test does not work if run as single test + test "logs can paginate", %{conn: conn} do + address = insert(:address) + + logs = + for x <- 0..50 do + tx = + :transaction + |> insert() + |> with_block() + + insert(:log, + transaction: tx, + index: x, + block: tx.block, + block_number: tx.block_number, + address: address + ) + end + + request = get(conn, "/api/v2/addresses/#{address.hash}/logs") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/logs", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + check_paginated_response(response, response_2nd_page, logs) + end + + test "logs can be filtered by topic", %{conn: conn} do + address = insert(:address) + + for x <- 0..20 do + tx = + :transaction + |> insert() + |> with_block() + + insert(:log, + transaction: tx, + index: x, + block: tx.block, + block_number: tx.block_number, + address: address + ) + end + + tx = + :transaction + |> insert() + |> with_block() + + log = + insert(:log, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + address: address, + first_topic: "0x123456789123456789" + ) + + request = get(conn, "/api/v2/addresses/#{address.hash}/logs?topic=0x123456789123456789") + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(log, Enum.at(response["items"], 0)) + end + end + + defp compare_item(%Transaction{} = transaction, json) do + assert to_string(transaction.hash) == json["hash"] + assert transaction.block_number == json["block"] + assert to_string(transaction.value.value) == json["value"] + assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"] + assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"] + end + + defp compare_item(%TokenTransfer{} = token_transfer, json) do + assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"] + assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"] + assert to_string(token_transfer.transaction_hash) == json["tx_hash"] + end + + defp compare_item(%InternalTransaction{} = internal_tx, json) do + assert internal_tx.block_number == json["block"] + assert to_string(internal_tx.gas) == json["gas_limit"] + assert internal_tx.index == json["index"] + assert to_string(internal_tx.transaction_hash) == json["transaction_hash"] + assert Address.checksum(internal_tx.from_address_hash) == json["from"]["hash"] + assert Address.checksum(internal_tx.to_address_hash) == json["to"]["hash"] + end + + defp compare_item(%Block{} = block, json) do + assert to_string(block.hash) == json["hash"] + assert block.number == json["height"] + end + + defp compare_item(%CurrentTokenBalance{} = ctb, json) do + assert to_string(ctb.value) == json["value"] + assert (ctb.token_id && to_string(ctb.token_id)) == json["token_id"] + compare_item(ctb.token, json["token"]) + end + + defp compare_item(%CoinBalance{} = cb, json) do + assert to_string(cb.value.value) == json["value"] + assert cb.block_number == json["block_number"] + + assert Jason.encode!(Repo.get_by(Block, number: cb.block_number).timestamp) =~ + String.replace(json["block_timestamp"], "Z", "") + end + + defp compare_item(%Token{} = token, json) do + assert Address.checksum(token.contract_address_hash) == json["address"] + assert to_string(token.symbol) == json["symbol"] + assert to_string(token.name) == json["name"] + assert to_string(token.type) == json["type"] + assert to_string(token.decimals) == json["decimals"] + assert (token.holder_count && to_string(token.holder_count)) == json["holders"] + assert Map.has_key?(json, "exchange_rate") + end + + defp compare_item(%Log{} = log, json) do + assert to_string(log.data) == json["data"] + assert log.index == json["index"] + assert Address.checksum(log.address_hash) == json["address"]["hash"] + end + + defp check_paginated_response(first_page_resp, second_page_resp, list) do + assert Enum.count(first_page_resp["items"]) == 50 + assert first_page_resp["next_page_params"] != nil + compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0)) + compare_item(Enum.at(list, 1), Enum.at(first_page_resp["items"], 49)) + + assert Enum.count(second_page_resp["items"]) == 1 + assert second_page_resp["next_page_params"] == nil + compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0)) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs new file mode 100644 index 000000000000..d16e457441bf --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs @@ -0,0 +1,329 @@ +defmodule BlockScoutWeb.API.V2.BlockControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.{Address, Block, Transaction} + + setup do + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id()) + + :ok + end + + describe "/blocks" do + test "empty lists", %{conn: conn} do + request = get(conn, "/api/v2/blocks") + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + + request = get(conn, "/api/v2/blocks", %{"type" => "uncle"}) + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + + request = get(conn, "/api/v2/blocks", %{"type" => "reorg"}) + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + + request = get(conn, "/api/v2/blocks", %{"type" => "block"}) + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get block", %{conn: conn} do + block = insert(:block) + + request = get(conn, "/api/v2/blocks") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(block, Enum.at(response["items"], 0)) + end + + test "type=block returns only consensus blocks", %{conn: conn} do + blocks = + 4 + |> insert_list(:block) + |> Enum.reverse() + + for index <- 0..3 do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) + end + + 2 + |> insert_list(:block, consensus: false) + + request = get(conn, "/api/v2/blocks", %{"type" => "block"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 4 + assert response["next_page_params"] == nil + + for index <- 0..3 do + compare_item(Enum.at(blocks, index), Enum.at(response["items"], index)) + end + end + + test "type=block can paginate", %{conn: conn} do + blocks = + 51 + |> insert_list(:block) + + filter = %{"type" => "block"} + + request = get(conn, "/api/v2/blocks", filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/blocks", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, blocks) + end + + test "type=reorg returns only non consensus blocks", %{conn: conn} do + blocks = + 5 + |> insert_list(:block) + + for index <- 0..3 do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) + end + + reorgs = + 4 + |> insert_list(:block, consensus: false) + |> Enum.reverse() + + request = get(conn, "/api/v2/blocks", %{"type" => "reorg"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 4 + assert response["next_page_params"] == nil + + for index <- 0..3 do + compare_item(Enum.at(reorgs, index), Enum.at(response["items"], index)) + end + end + + test "type=reorg can paginate", %{conn: conn} do + reorgs = + 51 + |> insert_list(:block, consensus: false) + + filter = %{"type" => "reorg"} + request = get(conn, "/api/v2/blocks", filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/blocks", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, reorgs) + end + + test "type=uncle returns only uncle blocks", %{conn: conn} do + blocks = + 4 + |> insert_list(:block) + |> Enum.reverse() + + uncles = + for index <- 0..3 do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) + uncle + end + |> Enum.reverse() + + 4 + |> insert_list(:block, consensus: false) + + request = get(conn, "/api/v2/blocks", %{"type" => "uncle"}) + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 4 + assert response["next_page_params"] == nil + + for index <- 0..3 do + compare_item(Enum.at(uncles, index), Enum.at(response["items"], index)) + end + end + + test "type=uncle can paginate", %{conn: conn} do + blocks = + 51 + |> insert_list(:block) + + uncles = + for index <- 0..50 do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) + uncle + end + + filter = %{"type" => "uncle"} + request = get(conn, "/api/v2/blocks", filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/blocks", Map.merge(response["next_page_params"], filter)) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, uncles) + end + end + + describe "/blocks/{block_hash_or_number}" do + test "return 422 on invalid parameter", %{conn: conn} do + request_1 = get(conn, "/api/v2/blocks/0x123123") + assert %{"message" => "Invalid hash"} = json_response(request_1, 422) + + request_2 = get(conn, "/api/v2/blocks/123qwe") + assert %{"message" => "Invalid number"} = json_response(request_2, 422) + end + + test "return 404 on non existing block", %{conn: conn} do + block = build(:block) + + request_1 = get(conn, "/api/v2/blocks/#{block.number}") + assert %{"message" => "Not found"} = json_response(request_1, 404) + + request_2 = get(conn, "/api/v2/blocks/#{block.hash}") + assert %{"message" => "Not found"} = json_response(request_2, 404) + end + + test "get the same blocks by hash and number", %{conn: conn} do + block = insert(:block) + + request_1 = get(conn, "/api/v2/blocks/#{block.number}") + assert response_1 = json_response(request_1, 200) + + request_2 = get(conn, "/api/v2/blocks/#{block.hash}") + assert response_2 = json_response(request_2, 200) + + assert response_2 == response_1 + compare_item(block, response_2) + end + end + + describe "/blocks/{block_hash_or_number}/transactions" do + test "return 422 on invalid parameter", %{conn: conn} do + request_1 = get(conn, "/api/v2/blocks/0x123123/transactions") + assert %{"message" => "Invalid hash"} = json_response(request_1, 422) + + request_2 = get(conn, "/api/v2/blocks/123qwe/transactions") + assert %{"message" => "Invalid number"} = json_response(request_2, 422) + end + + test "return 404 on non existing block", %{conn: conn} do + block = build(:block) + + request_1 = get(conn, "/api/v2/blocks/#{block.number}/transactions") + assert %{"message" => "Not found"} = json_response(request_1, 404) + + request_2 = get(conn, "/api/v2/blocks/#{block.hash}/transactions") + assert %{"message" => "Not found"} = json_response(request_2, 404) + end + + test "get empty list", %{conn: conn} do + block = insert(:block) + + request = get(conn, "/api/v2/blocks/#{block.number}/transactions") + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + + request = get(conn, "/api/v2/blocks/#{block.hash}/transactions") + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "get relevant tx", %{conn: conn} do + 10 + |> insert_list(:transaction) + |> with_block() + + block = insert(:block) + + tx = + :transaction + |> insert() + |> with_block(block) + + request = get(conn, "/api/v2/blocks/#{block.number}/transactions") + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(tx, Enum.at(response["items"], 0)) + + request = get(conn, "/api/v2/blocks/#{block.hash}/transactions") + assert response_1 = json_response(request, 200) + assert response_1 == response + end + + test "get txs with working next_page_params", %{conn: conn} do + 2 + |> insert_list(:transaction) + |> with_block() + + block = insert(:block) + + txs = + 51 + |> insert_list(:transaction) + |> with_block(block) + |> Enum.reverse() + + request = get(conn, "/api/v2/blocks/#{block.number}/transactions") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/blocks/#{block.number}/transactions", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + + request_1 = get(conn, "/api/v2/blocks/#{block.hash}/transactions") + assert response_1 = json_response(request_1, 200) + + assert response_1 == response + + request_2 = get(conn, "/api/v2/blocks/#{block.hash}/transactions", response_1["next_page_params"]) + assert response_2 = json_response(request_2, 200) + assert response_2 == response_2nd_page + end + end + + defp compare_item(%Block{} = block, json) do + assert to_string(block.hash) == json["hash"] + assert block.number == json["height"] + end + + defp compare_item(%Transaction{} = transaction, json) do + assert to_string(transaction.hash) == json["hash"] + assert transaction.block_number == json["block"] + assert to_string(transaction.value.value) == json["value"] + assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"] + assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"] + end + + defp check_paginated_response(first_page_resp, second_page_resp, list) do + assert Enum.count(first_page_resp["items"]) == 50 + assert first_page_resp["next_page_params"] != nil + compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0)) + compare_item(Enum.at(list, 1), Enum.at(first_page_resp["items"], 49)) + + assert Enum.count(second_page_resp["items"]) == 1 + assert second_page_resp["next_page_params"] == nil + compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0)) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/config_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/config_controller_test.exs new file mode 100644 index 000000000000..0c00722d58bb --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/config_controller_test.exs @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.API.V2.ConfigControllerTest do + use BlockScoutWeb.ConnCase + + describe "/config/json-rpc-url" do + test "get json rps url if set", %{conn: conn} do + url = "http://rps.url:1234/v1" + Application.put_env(:block_scout_web, :json_rpc, url) + + request = get(conn, "/api/v2/config/json-rpc-url") + + assert %{"json_rpc_url" => ^url} = json_response(request, 200) + end + + test "get empty json rps url if not set", %{conn: conn} do + Application.put_env(:block_scout_web, :json_rpc, nil) + + request = get(conn, "/api/v2/config/json-rpc-url") + + assert %{"json_rpc_url" => nil} = json_response(request, 200) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/main_page_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/main_page_controller_test.exs new file mode 100644 index 000000000000..b1e27673df8c --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/main_page_controller_test.exs @@ -0,0 +1,77 @@ +defmodule BlockScoutWeb.API.V2.MainPageControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.{Address, Block, Transaction} + + setup do + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) + + :ok + end + + describe "/main-page/blocks" do + test "get empty list when no blocks", %{conn: conn} do + request = get(conn, "/api/v2/main-page/blocks") + assert [] = json_response(request, 200) + end + + test "get last 4 blocks", %{conn: conn} do + blocks = insert_list(10, :block) |> Enum.take(-4) |> Enum.reverse() + + request = get(conn, "/api/v2/main-page/blocks") + assert response = json_response(request, 200) + assert Enum.count(response) == 4 + + for i <- 0..3 do + compare_item(Enum.at(blocks, i), Enum.at(response, i)) + end + end + end + + describe "/main-page/transactions" do + test "get empty list when no txs", %{conn: conn} do + request = get(conn, "/api/v2/main-page/transactions") + assert [] = json_response(request, 200) + end + + test "get last 5 txs", %{conn: conn} do + txs = insert_list(10, :transaction) |> with_block() |> Enum.take(-6) |> Enum.reverse() + + request = get(conn, "/api/v2/main-page/transactions") + assert response = json_response(request, 200) + assert Enum.count(response) == 6 + + for i <- 0..5 do + compare_item(Enum.at(txs, i), Enum.at(response, i)) + end + end + end + + describe "/main-page/indexing-status" do + test "get indexing status", %{conn: conn} do + request = get(conn, "/api/v2/main-page/indexing-status") + assert request = json_response(request, 200) + + assert Map.has_key?(request, "finished_indexing_blocks") + assert Map.has_key?(request, "finished_indexing") + assert Map.has_key?(request, "indexed_blocks_ratio") + assert Map.has_key?(request, "indexed_inernal_transactions_ratio") + end + end + + defp compare_item(%Block{} = block, json) do + assert to_string(block.hash) == json["hash"] + assert block.number == json["height"] + end + + defp compare_item(%Transaction{} = transaction, json) do + assert to_string(transaction.hash) == json["hash"] + assert transaction.block_number == json["block"] + assert to_string(transaction.value.value) == json["value"] + assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"] + assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"] + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs new file mode 100644 index 000000000000..211c68dc79f0 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs @@ -0,0 +1,147 @@ +defmodule BlockScoutWeb.API.V2.SearchControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.Address + + setup do + insert(:block) + insert(:unique_smart_contract) + insert(:unique_token) + insert(:transaction) + address = insert(:address) + insert(:unique_address_name, address: address) + + :ok + end + + describe "/search" do + test "search block", %{conn: conn} do + block = insert(:block) + + request = get(conn, "/api/v2/search?q=#{block.hash}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "block" + assert item["block_number"] == block.number + assert item["block_hash"] == to_string(block.hash) + assert item["url"] =~ to_string(block.hash) + + request = get(conn, "/api/v2/search?q=#{block.number}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "block" + assert item["block_number"] == block.number + assert item["block_hash"] == to_string(block.hash) + assert item["url"] =~ to_string(block.hash) + end + + test "search address", %{conn: conn} do + address = insert(:address) + name = insert(:unique_address_name, address: address) + + request = get(conn, "/api/v2/search?q=#{address.hash}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "address" + assert item["name"] == name.name + assert item["address"] == Address.checksum(address.hash) + assert item["url"] =~ Address.checksum(address.hash) + end + + test "search contract", %{conn: conn} do + contract = insert(:unique_smart_contract) + + request = get(conn, "/api/v2/search?q=#{contract.name}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "contract" + assert item["name"] == contract.name + assert item["address"] == Address.checksum(contract.address_hash) + assert item["url"] =~ Address.checksum(contract.address_hash) + end + + test "check pagination", %{conn: conn} do + name = "contract" + contracts = insert_list(51, :smart_contract, name: name) + + request = get(conn, "/api/v2/search?q=#{name}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "contract" + assert item["name"] == name + + request_2 = get(conn, "/api/v2/search", response["next_page_params"]) + assert response_2 = json_response(request_2, 200) + + assert Enum.count(response_2["items"]) == 1 + assert response_2["next_page_params"] == nil + + item = Enum.at(response_2["items"], 0) + + assert item["type"] == "contract" + assert item["name"] == name + + assert item not in response["items"] + end + + test "search token", %{conn: conn} do + token = insert(:unique_token) + + request = get(conn, "/api/v2/search?q=#{token.name}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "token" + assert item["name"] == token.name + assert item["symbol"] == token.symbol + assert item["address"] == Address.checksum(token.contract_address_hash) + assert item["token_url"] =~ Address.checksum(token.contract_address_hash) + assert item["address_url"] =~ Address.checksum(token.contract_address_hash) + end + + test "search transaction", %{conn: conn} do + tx = insert(:transaction) + + request = get(conn, "/api/v2/search?q=#{tx.hash}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "transaction" + assert item["tx_hash"] == to_string(tx.hash) + assert item["url"] =~ to_string(tx.hash) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs new file mode 100644 index 000000000000..803d5ad51ca3 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs @@ -0,0 +1,57 @@ +defmodule BlockScoutWeb.API.V2.StatsControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Counters.{AddressesCounter, AverageBlockTime} + + describe "/stats" do + setup do + start_supervised!(AddressesCounter) + start_supervised!(AverageBlockTime) + + Application.put_env(:explorer, AverageBlockTime, enabled: true) + + on_exit(fn -> + Application.put_env(:explorer, AverageBlockTime, enabled: false) + end) + + :ok + end + + test "get all fields", %{conn: conn} do + request = get(conn, "/api/v2/stats") + assert response = json_response(request, 200) + + assert Map.has_key?(response, "total_blocks") + assert Map.has_key?(response, "total_addresses") + assert Map.has_key?(response, "total_transactions") + assert Map.has_key?(response, "average_block_time") + assert Map.has_key?(response, "coin_price") + assert Map.has_key?(response, "total_gas_used") + assert Map.has_key?(response, "transactions_today") + assert Map.has_key?(response, "gas_used_today") + assert Map.has_key?(response, "gas_prices") + assert Map.has_key?(response, "static_gas_price") + assert Map.has_key?(response, "market_cap") + assert Map.has_key?(response, "network_utilization_percentage") + end + end + + describe "/stats/charts/market" do + test "get empty data", %{conn: conn} do + request = get(conn, "/api/v2/stats/charts/market") + assert response = json_response(request, 200) + + assert response["chart_data"] == [] + assert response["available_supply"] == 0 + end + end + + describe "/stats/charts/transactions" do + test "get empty data", %{conn: conn} do + request = get(conn, "/api/v2/stats/charts/transactions") + assert response = json_response(request, 200) + + assert response["chart_data"] == [] + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs new file mode 100644 index 000000000000..a0751c6bfee2 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -0,0 +1,228 @@ +defmodule BlockScoutWeb.API.V2.TokenControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.{Chain, Repo} + + alias Explorer.Chain.{Address, Token, TokenTransfer} + + alias Explorer.Chain.Address.CurrentTokenBalance + + describe "/tokens/{address_hash}" do + test "get 404 on non existing address", %{conn: conn} do + token = build(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/tokens/0x") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get token", %{conn: conn} do + token = insert(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}") + + assert response = json_response(request, 200) + + compare_item(token, response) + end + end + + describe "/tokens/{address_hash}/counters" do + test "get 404 on non existing address", %{conn: conn} do + token = build(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/counters") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/tokens/0x/counters") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get counters", %{conn: conn} do + token = insert(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/counters") + + assert response = json_response(request, 200) + + assert response["transfers_count"] == "0" + assert response["token_holders_count"] == "0" + end + + test "get not zero counters", %{conn: conn} do + contract_token_address = insert(:contract_address) + token = insert(:token, contract_address: contract_token_address) + + transaction = + :transaction + |> insert(to_address: contract_token_address) + |> with_block() + + insert_list( + 3, + :token_transfer, + transaction: transaction, + token_contract_address: contract_token_address + ) + + second_page_token_balances = + 1..5 + |> Enum.map( + &insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + value: &1 + 1000 + ) + ) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/counters") + + assert response = json_response(request, 200) + + assert response["transfers_count"] == "3" + assert response["token_holders_count"] == "5" + end + end + + describe "/tokens/{address_hash}/transfers" do + test "get 200 on non existing address", %{conn: conn} do + token = build(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/transfers") + + assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200) + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/tokens/0x/transfers") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get empty list", %{conn: conn} do + token = insert(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/transfers") + + assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200) + end + + test "check pagination", %{conn: conn} do + token = insert(:token) + + token_tranfers = + for _ <- 0..50 do + tx = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + token_contract_address: token.contract_address + ) + end + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/transfers") + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/tokens/#{token.contract_address.hash}/transfers", response["next_page_params"]) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_tranfers) + end + end + + describe "/tokens/{address_hash}/holders" do + test "get 200 on non existing address", %{conn: conn} do + token = build(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/holders") + + assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200) + end + + test "get 422 on invalid address", %{conn: conn} do + request = get(conn, "/api/v2/tokens/0x/holders") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get empty list", %{conn: conn} do + token = insert(:token) + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/holders") + + assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200) + end + + test "check pagination", %{conn: conn} do + token = insert(:token) + + token_balances = + for i <- 0..50 do + insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + value: i + 1000 + ) + end + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/holders") + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/tokens/#{token.contract_address.hash}/holders", response["next_page_params"]) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_balances) + end + end + + def compare_item(%Token{} = token, json) do + assert Address.checksum(token.contract_address.hash) == json["address"] + assert token.symbol == json["symbol"] + assert token.name == json["name"] + assert to_string(token.decimals) == json["decimals"] + assert token.type == json["type"] + assert token.holder_count == json["holders"] + assert to_string(token.total_supply) == json["total_supply"] + assert Map.has_key?(json, "exchange_rate") + end + + def compare_item(%TokenTransfer{} = token_transfer, json) do + assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"] + assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"] + assert to_string(token_transfer.transaction_hash) == json["tx_hash"] + end + + def compare_item(%CurrentTokenBalance{} = ctb, json) do + assert Address.checksum(ctb.address_hash) == json["address"]["hash"] + assert ctb.token_id == json["token_id"] + assert to_string(ctb.value) == json["value"] + compare_item(Repo.preload(ctb, [{:token, :contract_address}]).token, json["token"]) + end + + defp check_paginated_response(first_page_resp, second_page_resp, list) do + assert Enum.count(first_page_resp["items"]) == 50 + assert first_page_resp["next_page_params"] != nil + compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0)) + compare_item(Enum.at(list, 1), Enum.at(first_page_resp["items"], 49)) + + assert Enum.count(second_page_resp["items"]) == 1 + assert second_page_resp["next_page_params"] == nil + compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0)) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs new file mode 100644 index 000000000000..b7693129ffbc --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs @@ -0,0 +1,576 @@ +defmodule BlockScoutWeb.API.V2.TransactionControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.{Address, InternalTransaction, Log, TokenTransfer, Transaction} + + setup do + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) + + :ok + end + + describe "/transactions" do + test "empty list", %{conn: conn} do + request = get(conn, "/api/v2/transactions") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "non empty list", %{conn: conn} do + 1 + |> insert_list(:transaction) + |> with_block() + + request = get(conn, "/api/v2/transactions") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + end + + test "txs with next_page_params", %{conn: conn} do + txs = + 51 + |> insert_list(:transaction) + |> with_block() + + request = get(conn, "/api/v2/transactions") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/transactions", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test "filter=pending", %{conn: conn} do + pending_txs = + 51 + |> insert_list(:transaction) + + _mined_txs = + 51 + |> insert_list(:transaction) + |> with_block() + + filter = %{"filter" => "pending"} + + request = get(conn, "/api/v2/transactions", filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/transactions", Map.merge(response["next_page_params"], filter)) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, pending_txs) + end + + test "filter=validated", %{conn: conn} do + _pending_txs = + 51 + |> insert_list(:transaction) + + mined_txs = + 51 + |> insert_list(:transaction) + |> with_block() + + filter = %{"filter" => "validated"} + + request = get(conn, "/api/v2/transactions", filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/transactions", Map.merge(response["next_page_params"], filter)) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, mined_txs) + end + end + + describe "/transactions/{tx_hash}" do + test "return 404 on non existing tx", %{conn: conn} do + tx = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "return 422 on invalid tx hash", %{conn: conn} do + request = get(conn, "/api/v2/transactions/0x") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "return existing tx", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + request = get(conn, "/api/v2/transactions/" <> to_string(tx.hash)) + + assert response = json_response(request, 200) + compare_item(tx, response) + end + end + + describe "/transactions/{tx_hash}/internal-transactions" do + test "return 404 on non existing tx", %{conn: conn} do + tx = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "return 422 on invalid tx hash", %{conn: conn} do + request = get(conn, "/api/v2/transactions/0x/internal-transactions") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "return empty list", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "return relevant internal transaction", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + transaction: tx, + index: 0, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: 0 + ) + + internal_tx = + insert(:internal_transaction, + transaction: tx, + index: 1, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: 1 + ) + + tx_1 = + :transaction + |> insert() + |> with_block() + + 0..5 + |> Enum.map(fn index -> + insert(:internal_transaction, + transaction: tx_1, + index: index, + block_number: tx_1.block_number, + transaction_index: tx_1.index, + block_hash: tx_1.block_hash, + block_index: index + ) + end) + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(internal_tx, Enum.at(response["items"], 0)) + end + + test "return list with next_page_params", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + transaction: tx, + index: 0, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: 0 + ) + + internal_txs = + 51..1 + |> Enum.map(fn index -> + insert(:internal_transaction, + transaction: tx, + index: index, + block_number: tx.block_number, + transaction_index: tx.index, + block_hash: tx.block_hash, + block_index: index + ) + end) + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions", response["next_page_params"]) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, internal_txs) + end + end + + describe "/transactions/{tx_hash}/logs" do + test "return 404 on non existing tx", %{conn: conn} do + tx = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "return 422 on invalid tx hash", %{conn: conn} do + request = get(conn, "/api/v2/transactions/0x/logs") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "return empty list", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "return relevant log", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + log = + insert(:log, + transaction: tx, + index: 1, + block: tx.block, + block_number: tx.block_number + ) + + tx_1 = + :transaction + |> insert() + |> with_block() + + 0..5 + |> Enum.map(fn index -> + insert(:log, + transaction: tx_1, + index: index, + block: tx_1.block, + block_number: tx_1.block_number + ) + end) + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(log, Enum.at(response["items"], 0)) + end + + test "return list with next_page_params", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + logs = + 50..0 + |> Enum.map(fn index -> + insert(:log, + transaction: tx, + index: index, + block: tx.block, + block_number: tx.block_number + ) + end) + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs", response["next_page_params"]) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, logs) + end + end + + describe "/transactions/{tx_hash}/token-transfers" do + test "return 404 on non existing tx", %{conn: conn} do + tx = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "return 422 on invalid tx hash", %{conn: conn} do + request = get(conn, "/api/v2/transactions/0x/token-transfers") + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "return empty list", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "return relevant token transfer", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + token_transfer = insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + + tx_1 = + :transaction + |> insert() + |> with_block() + + insert_list(6, :token_transfer, transaction: tx_1, block: tx_1.block, block_number: tx_1.block_number) + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + compare_item(token_transfer, Enum.at(response["items"], 0)) + end + + test "return list with next_page_params", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + token_transfers = + insert_list(51, :token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + |> Enum.reverse() + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_transfers) + end + + test "check filters", %{conn: conn} do + tx = + :transaction + |> insert() + |> with_block() + + erc_1155_token = insert(:token, type: "ERC-1155") + + erc_1155_tt = + for x <- 0..50 do + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + token_contract_address: erc_1155_token.contract_address, + token_ids: [x] + ) + end + |> Enum.reverse() + + erc_721_token = insert(:token, type: "ERC-721") + + erc_721_tt = + for x <- 0..50 do + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + token_contract_address: erc_721_token.contract_address, + token_ids: [x] + ) + end + |> Enum.reverse() + + erc_20_token = insert(:token, type: "ERC-20") + + erc_20_tt = + for _ <- 0..50 do + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + token_contract_address: erc_20_token.contract_address + ) + end + |> Enum.reverse() + + # -- ERC-20 -- + filter = %{"type" => "ERC-20"} + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_20_tt) + # -- ------ -- + + # -- ERC-721 -- + filter = %{"type" => "ERC-721"} + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_721_tt) + # -- ------ -- + + # -- ERC-1155 -- + filter = %{"type" => "ERC-1155"} + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, erc_1155_tt) + # -- ------ -- + + # two filters simultaneously + filter = %{"type" => "ERC-1155,ERC-20"} + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(erc_1155_tt, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(erc_1155_tt, 1), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 50 + assert response_2nd_page["next_page_params"] != nil + compare_item(Enum.at(erc_1155_tt, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(erc_20_tt, 50), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(erc_20_tt, 2), Enum.at(response_2nd_page["items"], 49)) + + request_3rd_page = + get( + conn, + "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + Map.merge(response_2nd_page["next_page_params"], filter) + ) + + assert response_3rd_page = json_response(request_3rd_page, 200) + assert Enum.count(response_3rd_page["items"]) == 2 + assert response_3rd_page["next_page_params"] == nil + compare_item(Enum.at(erc_20_tt, 1), Enum.at(response_3rd_page["items"], 0)) + compare_item(Enum.at(erc_20_tt, 0), Enum.at(response_3rd_page["items"], 1)) + end + end + + defp compare_item(%Transaction{} = transaction, json) do + assert to_string(transaction.hash) == json["hash"] + assert transaction.block_number == json["block"] + assert to_string(transaction.value.value) == json["value"] + assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"] + assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"] + end + + defp compare_item(%InternalTransaction{} = internal_tx, json) do + assert internal_tx.block_number == json["block"] + assert to_string(internal_tx.gas) == json["gas_limit"] + assert internal_tx.index == json["index"] + assert to_string(internal_tx.transaction_hash) == json["transaction_hash"] + assert Address.checksum(internal_tx.from_address_hash) == json["from"]["hash"] + assert Address.checksum(internal_tx.to_address_hash) == json["to"]["hash"] + end + + defp compare_item(%Log{} = log, json) do + assert to_string(log.data) == json["data"] + assert log.index == json["index"] + assert Address.checksum(log.address_hash) == json["address"]["hash"] + end + + defp compare_item(%TokenTransfer{} = token_transfer, json) do + assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"] + assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"] + assert to_string(token_transfer.transaction_hash) == json["tx_hash"] + end + + defp check_paginated_response(first_page_resp, second_page_resp, txs) do + assert Enum.count(first_page_resp["items"]) == 50 + assert first_page_resp["next_page_params"] != nil + compare_item(Enum.at(txs, 50), Enum.at(first_page_resp["items"], 0)) + compare_item(Enum.at(txs, 1), Enum.at(first_page_resp["items"], 49)) + + assert Enum.count(second_page_resp["items"]) == 1 + assert second_page_resp["next_page_params"] == nil + compare_item(Enum.at(txs, 0), Enum.at(second_page_resp["items"], 0)) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs new file mode 100644 index 000000000000..a78251a3e109 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs @@ -0,0 +1,28 @@ +defmodule BlockScoutWeb.Tokens.Instance.TransferControllerTest do + use BlockScoutWeb.ConnCase, async: false + + describe "GET token-transfers/2" do + test "fetches the instance", %{conn: conn} do + token = insert(:token) + + contract_address_hash = token.contract_address_hash + + token_id = Decimal.new(10) + + insert(:token_instance, + token_contract_address_hash: contract_address_hash, + token_id: token_id + ) + + conn = get(conn, "/token/#{contract_address_hash}/instance/#{token_id}/token-transfers") + + assert %{ + assigns: %{ + token_instance: %{ + instance: %{token_contract_address_hash: ^contract_address_hash, token_id: ^token_id} + } + } + } = conn + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs index 10d8831331f7..2c6123f77aac 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs @@ -3,21 +3,24 @@ defmodule BlockScoutWeb.Tokens.InstanceControllerTest do describe "GET show/2" do test "redirects with valid params", %{conn: conn} do - contract_address = insert(:address) + token = insert(:token) - insert(:token, contract_address: contract_address) + contract_address_hash = token.contract_address_hash token_id = 10 - insert(:token_transfer, - from_address: contract_address, - token_contract_address: contract_address, + insert(:token_instance, + token_contract_address_hash: contract_address_hash, token_id: token_id ) - conn = get(conn, token_instance_path(BlockScoutWeb.Endpoint, :show, to_string(contract_address.hash), token_id)) + conn = get(conn, token_instance_path(BlockScoutWeb.Endpoint, :show, to_string(contract_address_hash), token_id)) assert conn.status == 302 + + assert get_resp_header(conn, "location") == [ + "/token/#{contract_address_hash}/instance/#{token_id}/token-transfers" + ] end end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs index e1591a4db91b..799b54da797c 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs @@ -55,7 +55,7 @@ defmodule BlockScoutWeb.Tokens.InventoryControllerTest do transaction: transaction, token_contract_address: token.contract_address, token: token, - token_id: i + 1000 + token_ids: [i + 1000] ) insert( @@ -94,7 +94,7 @@ defmodule BlockScoutWeb.Tokens.InventoryControllerTest do transaction: transaction, token_contract_address: token.contract_address, token: token, - token_id: i + 1000 + token_ids: [i + 1000] ) insert( diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs new file mode 100644 index 000000000000..0090ceaa9ee1 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs @@ -0,0 +1,215 @@ +defmodule BlockScoutWeb.TransactionStateControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + import BlockScoutWeb.WebRouter.Helpers, only: [transaction_state_path: 3] + import BlockScoutWeb.WeiHelpers, only: [format_wei_value: 2] + import EthereumJSONRPC, only: [integer_to_quantity: 1] + alias Explorer.Chain.Wei + + describe "GET index/3" do + test "loads existing transaction", %{conn: conn} do + transaction = insert(:transaction) + conn = get(conn, transaction_state_path(conn, :index, transaction.hash)) + + assert html_response(conn, 200) + end + + test "with missing transaction", %{conn: conn} do + hash = transaction_hash() + conn = get(conn, transaction_state_path(BlockScoutWeb.Endpoint, :index, hash)) + + assert html_response(conn, 404) + end + + test "with invalid transaction hash", %{conn: conn} do + conn = get(conn, transaction_state_path(BlockScoutWeb.Endpoint, :index, "nope")) + + assert html_response(conn, 422) + end + + test "with duplicated from, to or miner fields", %{conn: conn} do + address = insert(:address) + + insert(:block) + block = insert(:block, miner: address) + + insert(:fetched_balance, + address_hash: address.hash, + value: 1_000_000, + block_number: block.number - 1 + ) + + transaction = insert(:transaction, from_address: address, to_address: address) |> with_block(block, status: :ok) + + conn = get(conn, transaction_state_path(conn, :index, transaction), %{type: "JSON"}) + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + + assert(items |> Enum.filter(fn item -> item != nil end) |> length() == 1) + end + + test "returns state changes for the transaction with contract creation", %{conn: conn} do + block = insert(:block) + + contract_address = insert(:contract_address) + + transaction = + :transaction + |> insert(to_address: nil) + |> with_contract_creation(contract_address) + |> with_block(insert(:block)) + + insert(:fetched_balance, + address_hash: transaction.from_address_hash, + value: 1_000_000, + block_number: block.number + ) + + insert(:fetched_balance, + address_hash: transaction.block.miner_hash, + value: 1_000_000, + block_number: block.number + ) + + conn = get(conn, transaction_state_path(conn, :index, transaction), %{type: "JSON"}) + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + + assert(items |> Enum.filter(fn item -> item != nil end) |> length() == 2) + end + + test "returns fetched state changes for the transaction with token transfer", %{conn: conn} do + block = insert(:block) + address_a = insert(:address) + address_b = insert(:address) + token = insert(:token, type: "ERC-20") + + insert(:fetched_balance, + address_hash: address_a.hash, + value: 1_000_000_000_000_000_000, + block_number: block.number + ) + + insert(:fetched_balance, + address_hash: address_b.hash, + value: 2_000_000_000_000_000_000, + block_number: block.number + ) + + transaction = + :transaction + |> insert(from_address: address_a, to_address: address_b, value: 1000) + |> with_block(status: :ok) + + insert(:fetched_balance, + address_hash: transaction.block.miner_hash, + value: 2_500_000, + block_number: block.number + ) + + token_transfer = + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + token: token, + token_contract_address: token.contract_address + ) + + insert( + :token_balance, + address: token_transfer.from_address, + token: token, + token_contract_address_hash: token.contract_address_hash, + value: 3_000_000, + block_number: block.number + ) + + insert( + :token_balance, + address: token_transfer.to_address, + token: token, + token_contract_address_hash: token.contract_address_hash, + value: 1000, + block_number: block.number + ) + + # to check if we can display transaction overview + get(conn, transaction_state_path(conn, :index, transaction)) + conn = get(conn, transaction_state_path(conn, :index, transaction), %{type: "JSON"}) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + full_text = Enum.join(items) + + assert(String.contains?(full_text, format_wei_value(%Wei{value: Decimal.new(1, 1, 18)}, :ether))) + + assert(String.contains?(full_text, format_wei_value(%Wei{value: Decimal.new(1, 2, 18)}, :ether))) + + assert(length(items) == 5) + end + + test "fetch coin balances if needed", %{conn: conn} do + EthereumJSONRPC.Mox + |> stub(:json_rpc, fn + [%{id: id, method: "eth_getBalance", params: _}], _options -> + {:ok, [%{id: id, result: integer_to_quantity(123)}]} + + [%{id: id, method: "eth_getBlockByNumber", params: _}], _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "author" => "0x0000000000000000000000000000000000000000", + "difficulty" => "0x20000", + "extraData" => "0x", + "gasLimit" => "0x663be0", + "gasUsed" => "0x0", + "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + "logsBloom" => + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner" => "0x0000000000000000000000000000000000000000", + "number" => integer_to_quantity(1), + "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sealFields" => [ + "0x80", + "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ], + "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "signature" => + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "size" => "0x215", + "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", + "step" => "0", + "timestamp" => "0x0", + "totalDifficulty" => "0x20000", + "transactions" => [], + "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles" => [] + } + } + ]} + end) + + insert(:block) + insert(:block) + address_a = insert(:address) + address_b = insert(:address) + + transaction = + :transaction + |> insert(from_address: address_a, to_address: address_b, value: 1000) + |> with_block(status: :ok) + + conn = get(conn, transaction_state_path(conn, :index, transaction), %{type: "JSON"}) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + full_text = Enum.join(items) + + assert(String.contains?(full_text, format_wei_value(%Wei{value: Decimal.new(123)}, :ether))) + assert(length(items) == 3) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs new file mode 100644 index 000000000000..dd5327ca7cca --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs @@ -0,0 +1,155 @@ +defmodule BlockScoutWeb.VerifiedContractsControllerTest do + use BlockScoutWeb.ConnCase + + import BlockScoutWeb.WebRouter.Helpers, only: [verified_contracts_path: 2, verified_contracts_path: 3] + + alias Explorer.Chain.SmartContract + + alias Explorer.Chain.Cache.{ + ContractsCounter, + NewContractsCounter, + NewVerifiedContractsCounter, + VerifiedContractsCounter + } + + describe "GET index/2" do + test "returns 200", %{conn: conn} do + start_supervised!(ContractsCounter) + ContractsCounter.consolidate() + start_supervised!(NewContractsCounter) + NewContractsCounter.consolidate() + start_supervised!(NewVerifiedContractsCounter) + NewVerifiedContractsCounter.consolidate() + start_supervised!(VerifiedContractsCounter) + VerifiedContractsCounter.consolidate() + + conn = get(conn, verified_contracts_path(conn, :index)) + + assert html_response(conn, 200) + end + + test "returns all contracts", %{conn: conn} do + insert_list(4, :smart_contract) + + conn = get(conn, verified_contracts_path(conn, :index), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 4 + end + + test "returns next page of results based on last verified contract", %{conn: conn} do + insert_list(50, :smart_contract) + + contract = insert(:smart_contract) + + conn = + get(conn, verified_contracts_path(conn, :index), %{ + "type" => "JSON", + "smart_contract_id" => Integer.to_string(contract.id) + }) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 50 + end + + test "next_page_path exist if not on last page", %{conn: conn} do + %SmartContract{id: id} = + 60 + |> insert_list(:smart_contract) + |> Enum.fetch!(10) + + conn = get(conn, verified_contracts_path(conn, :index), %{"type" => "JSON"}) + + expected_path = + verified_contracts_path(conn, :index, %{ + smart_contract_id: id, + items_count: "50" + }) + + assert Map.get(json_response(conn, 200), "next_page_path") == expected_path + end + + test "next_page_path is empty if on last page", %{conn: conn} do + insert(:smart_contract) + + conn = get(conn, verified_contracts_path(conn, :index), %{"type" => "JSON"}) + + refute conn |> json_response(200) |> Map.get("next_page_path") + end + + test "returns solidity contracts", %{conn: conn} do + insert(:smart_contract, is_vyper_contract: true) + %SmartContract{address_hash: solidity_hash} = insert(:smart_contract, is_vyper_contract: false) + + path = + verified_contracts_path(conn, :index, %{ + "filter" => "solidity", + "type" => "JSON" + }) + + conn = get(conn, path) + + [smart_contracts_tile] = json_response(conn, 200)["items"] + + assert String.contains?(smart_contracts_tile, "data-identifier-hash=\"#{to_string(solidity_hash)}\"") + end + + test "returns vyper contract", %{conn: conn} do + %SmartContract{address_hash: vyper_hash} = insert(:smart_contract, is_vyper_contract: true) + insert(:smart_contract, is_vyper_contract: false) + + path = + verified_contracts_path(conn, :index, %{ + "filter" => "vyper", + "type" => "JSON" + }) + + conn = get(conn, path) + + [smart_contracts_tile] = json_response(conn, 200)["items"] + + assert String.contains?(smart_contracts_tile, "data-identifier-hash=\"#{to_string(vyper_hash)}\"") + end + + test "returns search results by name", %{conn: conn} do + insert(:smart_contract) + insert(:smart_contract) + insert(:smart_contract) + contract_name = "qwertyufhgkhiop" + %SmartContract{address_hash: hash} = insert(:smart_contract, name: contract_name) + + path = + verified_contracts_path(conn, :index, %{ + "search" => contract_name, + "type" => "JSON" + }) + + conn = get(conn, path) + + [smart_contracts_tile] = json_response(conn, 200)["items"] + + assert String.contains?(smart_contracts_tile, "data-identifier-hash=\"#{to_string(hash)}\"") + end + + test "returns search results by address", %{conn: conn} do + insert(:smart_contract) + insert(:smart_contract) + insert(:smart_contract) + %SmartContract{address_hash: hash} = insert(:smart_contract) + + path = + verified_contracts_path(conn, :index, %{ + "search" => to_string(hash), + "type" => "JSON" + }) + + conn = get(conn, path) + + [smart_contracts_tile] = json_response(conn, 200)["items"] + + assert String.contains?(smart_contracts_tile, "data-identifier-hash=\"#{to_string(hash)}\"") + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs index 0cab258891e3..6a8fd918a6a8 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs @@ -79,7 +79,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do session |> AddressPage.visit_page(address) - |> assert_text(AddressPage.balance(), "0.0000000000000005 Ether") + |> assert_text(AddressPage.balance(), "0.0000000000000005 ETH") end describe "viewing contract creator" do diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs index 83877c8e460a..4467f50b8a3f 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs @@ -27,9 +27,9 @@ defmodule BlockScoutWeb.ViewingAppTest do # |> insert() # |> with_block(block) - # assert Decimal.compare(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq + # assert Decimal.compare(Explorer.Chain.indexed_ratio_blocks(), Decimal.from_float(0.5)) == :eq - # insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + # insert(:pending_block_operation, block_hash: block.hash) # session # |> AppPage.visit_page() @@ -46,13 +46,13 @@ defmodule BlockScoutWeb.ViewingAppTest do # |> insert() # |> with_block(block) - # assert Decimal.compare(Explorer.Chain.indexed_ratio(), 1) == :eq + # assert Decimal.compare(Explorer.Chain.indexed_ratio_blocks(), 1) == :eq - # insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + # insert(:pending_block_operation, block_hash: block.hash) # session # |> AppPage.visit_page() - # |> assert_has(AppPage.indexed_status("Indexing Tokens")) + # |> assert_has(AppPage.indexed_status("Indexing Internal Transactions")) # end # test "updates blocks indexed percentage", %{session: session} do @@ -67,9 +67,9 @@ defmodule BlockScoutWeb.ViewingAppTest do # BlocksIndexedCounter.calculate_blocks_indexed() - # assert Decimal.compare(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq + # assert Decimal.compare(Explorer.Chain.indexed_ratio_blocks(), Decimal.from_float(0.5)) == :eq - # insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + # insert(:pending_block_operation, block_hash: block.hash) # session # |> AppPage.visit_page() @@ -96,7 +96,7 @@ defmodule BlockScoutWeb.ViewingAppTest do # assert Decimal.compare(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.9)) == :eq - # insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + # insert(:pending_block_operation, block_hash: block.hash) # session # |> AppPage.visit_page() @@ -106,7 +106,7 @@ defmodule BlockScoutWeb.ViewingAppTest do # BlocksIndexedCounter.calculate_blocks_indexed() - # assert_has(session, AppPage.indexed_status("Indexing Tokens")) + # assert_has(session, AppPage.indexed_status("Indexing Internal Transactions")) # end # test "removes message when chain is indexed", %{session: session} do @@ -121,20 +121,15 @@ defmodule BlockScoutWeb.ViewingAppTest do # block_hash = block.hash - # insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true) + # insert(:pending_block_operation, block_hash: block_hash) # BlocksIndexedCounter.calculate_blocks_indexed() - # assert Decimal.compare(Explorer.Chain.indexed_ratio(), 1) == :eq + # assert Decimal.compare(Explorer.Chain.indexed_ratio_blocks(), 1) == :eq # session # |> AppPage.visit_page() - # |> assert_has(AppPage.indexed_status("Indexing Tokens")) - - # Repo.update_all( - # from(p in PendingBlockOperation, where: p.block_hash == ^block_hash), - # set: [fetch_internal_transactions: false] - # ) + # |> assert_has(AppPage.indexed_status("Indexing Internal Transactions")) # BlocksIndexedCounter.calculate_blocks_indexed() diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs index 3a14474c2cab..5216623e7188 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs @@ -133,7 +133,7 @@ defmodule BlockScoutWeb.ViewingChainTest do transaction = :transaction |> insert(to_address: contract_token_address) - |> with_block(block) + |> with_block(block, status: :ok) insert_list( 3, diff --git a/apps/block_scout_web/test/block_scout_web/models/user_from_auth_test.exs b/apps/block_scout_web/test/block_scout_web/models/user_from_auth_test.exs new file mode 100644 index 000000000000..2d13f116009e --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/models/user_from_auth_test.exs @@ -0,0 +1,107 @@ +defmodule UserFromAuthTest do + use Explorer.DataCase + + alias BlockScoutWeb.Models.UserFromAuth + alias Explorer.Account.Identity + alias Explorer.Account.Watchlist + alias Explorer.Repo + alias Ueberauth.Auth + alias Ueberauth.Auth.Info + alias Ueberauth.Strategy.Auth0 + + describe "get user info" do + test "from github" do + auth = %Auth{ + info: %Info{ + birthday: nil, + description: nil, + email: "john@blockscout.com", + first_name: nil, + image: "https://avatars.githubusercontent.com/u/666666=4", + last_name: nil, + location: nil, + name: "John Snow", + nickname: "johnnny", + phone: nil, + urls: %{profile: nil, website: nil} + }, + provider: :auth0, + strategy: Auth0, + uid: "github|666666" + } + + user_data = UserFromAuth.find_or_create(auth) + + %{ + id: identity_id, + email: "john@blockscout.com", + name: "John Snow", + uid: "github|666666" + } = Identity |> first |> Repo.account_repo().one() + + %{ + id: watchlist_id, + identity_id: ^identity_id, + name: "default" + } = Watchlist |> first |> Repo.account_repo().one() + + assert {:ok, + %{ + avatar: "https://avatars.githubusercontent.com/u/666666=4", + email: "john@blockscout.com", + id: ^identity_id, + name: "John Snow", + nickname: "johnnny", + uid: "github|666666", + watchlist_id: ^watchlist_id + }} = user_data + end + + test "from google" do + auth = %Auth{ + info: %Info{ + birthday: nil, + description: nil, + email: "john@blockscout.com", + first_name: "John", + image: "https://lh3.googleusercontent.com/a/xxx666-yyy777=s99-c", + last_name: "Snow", + location: nil, + name: "John Snow", + nickname: "johnnny", + phone: nil, + urls: %{profile: nil, website: nil} + }, + provider: :auth0, + strategy: Auth0, + uid: "google-oauth2|666666" + } + + user_data = UserFromAuth.find_or_create(auth) + + %{ + id: identity_id, + email: "john@blockscout.com", + name: "John Snow", + uid: "google-oauth2|666666" + } = Identity |> first |> Repo.account_repo().one() + + %{ + id: watchlist_id, + identity_id: ^identity_id, + name: "default" + } = Watchlist |> first |> Repo.account_repo().one() + + assert {:ok, + %{ + avatar: "https://lh3.googleusercontent.com/a/xxx666-yyy777=s99-c", + email: "john@blockscout.com", + id: ^identity_id, + name: "John Snow", + nickname: "johnnny", + uid: "google-oauth2|666666", + watchlist_id: ^watchlist_id + }} = user_data + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs index 64adae6fb9aa..134d1b7f349f 100644 --- a/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs +++ b/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs @@ -326,6 +326,68 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do assert Enum.all?(transactions, &(&1["node"]["block_number"] == third_block.number)) end + test "transactions are ordered by ascending block and index", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + third_block = insert(:block) + + address = insert(:address) + + 3 + |> insert_list(:transaction, from_address: address) + |> with_block(second_block) + + 3 + |> insert_list(:transaction, from_address: address) + |> with_block(third_block) + + 3 + |> insert_list(:transaction, from_address: address) + |> with_block(first_block) + + query = """ + query ($hash: AddressHash!, $first: Int!) { + address(hash: $hash) { + transactions(first: $first, order: ASC) { + edges { + node { + hash + block_number + index + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(address.hash), + "first" => 3 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + %{ + "data" => %{ + "address" => %{ + "transactions" => %{ + "edges" => transactions + } + } + } + } = json_response(conn, 200) + + block_number_and_index_order = + Enum.map(transactions, fn transaction -> + {transaction["node"]["block_number"], transaction["node"]["index"]} + end) + + assert block_number_and_index_order == Enum.sort(block_number_and_index_order, &(&1 < &2)) + assert length(transactions) == 3 + assert Enum.all?(transactions, &(&1["node"]["block_number"] == first_block.number)) + end + test "complexity correlates to 'first' or 'last' arguments", %{conn: conn} do address = build(:address) diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs index 1b02348d2328..75a58b844843 100644 --- a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs +++ b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do describe "token_transfers field" do test "with valid argument, returns all expected fields", %{conn: conn} do transaction = insert(:transaction) - token_transfer = insert(:token_transfer, transaction: transaction) + token_transfer = insert(:token_transfer, transaction: transaction, token_ids: [5], amounts: [10]) address_hash = to_string(token_transfer.token_contract_address_hash) query = """ @@ -13,9 +13,11 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do edges { node { amount + amounts block_number log_index token_id + token_ids from_address_hash to_address_hash token_contract_address_hash @@ -40,9 +42,11 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do %{ "node" => %{ "amount" => to_string(token_transfer.amount), + "amounts" => Enum.map(token_transfer.amounts, &to_string/1), "block_number" => token_transfer.block_number, "log_index" => token_transfer.log_index, "token_id" => token_transfer.token_id, + "token_ids" => Enum.map(token_transfer.token_ids, &to_string/1), "from_address_hash" => to_string(token_transfer.from_address_hash), "to_address_hash" => to_string(token_transfer.to_address_hash), "token_contract_address_hash" => to_string(token_transfer.token_contract_address_hash), diff --git a/apps/block_scout_web/test/block_scout_web/views/address_coin_balance_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_coin_balance_view_test.exs index 348dc96f6cf8..bc07e1c4feeb 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_coin_balance_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_coin_balance_view_test.exs @@ -8,13 +8,13 @@ defmodule BlockScoutWeb.AddressCoinBalanceViewTest do test "format the wei value in ether" do wei = Wei.from(Decimal.new(1_340_000_000), :gwei) - assert AddressCoinBalanceView.format(wei) == "1.34 Ether" + assert AddressCoinBalanceView.format(wei) == "1.34 ETH" end test "format negative values" do wei = Wei.from(Decimal.new(-1_340_000_000), :gwei) - assert AddressCoinBalanceView.format(wei) == "-1.34 Ether" + assert AddressCoinBalanceView.format(wei) == "-1.34 ETH" end end @@ -50,13 +50,13 @@ defmodule BlockScoutWeb.AddressCoinBalanceViewTest do test "format positive values" do value = Decimal.new(1_340_000_000_000_000_000) - assert AddressCoinBalanceView.format_delta(value) == "1.34 Ether" + assert AddressCoinBalanceView.format_delta(value) == "1.34 ETH" end test "format negative values" do value = Decimal.new(-1_340_000_000_000_000_000) - assert AddressCoinBalanceView.format_delta(value) == "1.34 Ether" + assert AddressCoinBalanceView.format_delta(value) == "1.34 ETH" end end end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs index cd57a45f95af..1ac75c92b400 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs @@ -20,9 +20,11 @@ defmodule BlockScoutWeb.AddressTokenBalanceViewTest do token_balance_a = build(:token_balance, token: build(:token, type: "ERC-20")) token_balance_b = build(:token_balance, token: build(:token, type: "ERC-721")) - token_balances = [{token_balance_a, %{}}, {token_balance_b, %{}}] + token_balances = [{token_balance_a, token_balance_a.token}, {token_balance_b, token_balance_b.token}] - assert AddressTokenBalanceView.filter_by_type(token_balances, "ERC-20") == [{token_balance_a, %{}}] + assert AddressTokenBalanceView.filter_by_type(token_balances, "ERC-20") == [ + {token_balance_a, token_balance_a.token} + ] end end diff --git a/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs index 764c98206323..cc1c9f129712 100644 --- a/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs @@ -40,11 +40,11 @@ defmodule BlockScoutWeb.ApiDocsViewTest do describe "blockscout_url/2" do setup do - on_exit(fn -> - Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, - url: [scheme: "http", host: "localhost", api_path: nil, path: nil] - ) - end) + original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint) + + on_exit(fn -> Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, original) end) + + :ok end test "set_path = true returns url with path" do diff --git a/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs index d707404628cd..eea5baf28420 100644 --- a/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs @@ -34,8 +34,9 @@ defmodule BlockScoutWeb.BlockViewTest do test "returns Uncle" do uncle = insert(:block, consensus: false) insert(:block_second_degree_relation, uncle_hash: uncle.hash) + preloaded = Repo.preload(uncle, :nephews) - assert BlockView.block_type(uncle) == "Uncle" + assert BlockView.block_type(preloaded) == "Uncle" end end @@ -91,7 +92,7 @@ defmodule BlockScoutWeb.BlockViewTest do block = Repo.preload(block, :rewards) - assert BlockView.combined_rewards_value(block) == "3.000042 Ether" + assert BlockView.combined_rewards_value(block) == "3.000042 ETH" end end end diff --git a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs index 9b16ef3a542d..956ef4f3ae8f 100644 --- a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs @@ -136,7 +136,7 @@ defmodule BlockScoutWeb.TransactionViewTest do gas_used: nil ) - expected_value = "Max of 0.009 Ether" + expected_value = "Max of 0.009 ETH" assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether) end @@ -144,7 +144,7 @@ defmodule BlockScoutWeb.TransactionViewTest do {:ok, gas_price} = Wei.cast(3_000_000_000) transaction = build(:transaction, gas_price: gas_price, gas_used: Decimal.from_float(1_034_234.0)) - expected_value = "0.003102702 Ether" + expected_value = "0.003102702 ETH" assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether) end end @@ -192,7 +192,7 @@ defmodule BlockScoutWeb.TransactionViewTest do |> insert() |> with_block(block, status: :error) - insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block.hash) status = TransactionView.transaction_status(transaction) assert TransactionView.formatted_result(status) == "Error: (Awaiting internal transactions for reason)" @@ -276,9 +276,9 @@ defmodule BlockScoutWeb.TransactionViewTest do token = insert(:token) - token_transfer1 = insert(:token_transfer, transaction: transaction, token: token, token_id: 1, amount: nil) - token_transfer2 = insert(:token_transfer, transaction: transaction, token: token, token_id: 2, amount: nil) - token_transfer3 = insert(:token_transfer, transaction: transaction, token: token, token_id: 3, amount: nil) + token_transfer1 = insert(:token_transfer, transaction: transaction, token: token, token_ids: [1], amount: nil) + token_transfer2 = insert(:token_transfer, transaction: transaction, token: token, token_ids: [2], amount: nil) + token_transfer3 = insert(:token_transfer, transaction: transaction, token: token, token_ids: [3], amount: nil) result = TransactionView.aggregate_token_transfers([token_transfer1, token_transfer2, token_transfer3]) diff --git a/apps/block_scout_web/test/support/conn_case.ex b/apps/block_scout_web/test/support/conn_case.ex index f9ae650bf9db..5ae6216f0c01 100644 --- a/apps/block_scout_web/test/support/conn_case.ex +++ b/apps/block_scout_web/test/support/conn_case.ex @@ -22,6 +22,7 @@ defmodule BlockScoutWeb.ConnCase do import Phoenix.ConnTest import BlockScoutWeb.Router.Helpers import BlockScoutWeb.WebRouter.Helpers, except: [static_path: 2] + import Bureaucrat.Helpers # The default endpoint for testing @endpoint BlockScoutWeb.Endpoint @@ -36,9 +37,11 @@ defmodule BlockScoutWeb.ConnCase do @dialyzer {:nowarn_function, __ex_unit_setup_0: 1} setup tags do :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo) + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Account) unless tags[:async] do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, {:shared, self()}) end Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id()) diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index cf64b94ffd29..a99ba9a234d5 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -11,13 +11,24 @@ Application.put_env(:wallaby, :base_url, BlockScoutWeb.Endpoint.url()) {:ok, _} = Application.ensure_all_started(:ex_machina) -ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) +Bureaucrat.start( + writer: Bureaucrat.ApiBlueprintWriter, + default_path: "API blueprint.md", + env_var: "DOC" +) + +# Bureaucrat.start() + +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter, Bureaucrat.Formatter]) ExUnit.start() Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :manual) Absinthe.Test.prime(BlockScoutWeb.Schema) Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport) + +Mox.defmock(BlockScoutWeb.TestCaptchaHelper, for: BlockScoutWeb.CaptchaHelper) diff --git a/apps/ethereum_jsonrpc/README.md b/apps/ethereum_jsonrpc/README.md index 5d9d469ea435..945c793da83e 100644 --- a/apps/ethereum_jsonrpc/README.md +++ b/apps/ethereum_jsonrpc/README.md @@ -22,24 +22,24 @@ library (`HTTPoison`), which forwards the options down to `:hackney`. ## Testing -### Parity +### Nethermind #### Mox **This is the default setup. `mix test` will work on its own, but to be explicit, use the following setup**: ```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.Mox +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Nethermind.Mox export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox -mix test --exclude no_parity +mix test --exclude no_nethermind ``` #### HTTP / WebSocket ```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket -export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Parity -mix test --exclude no_parity +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Nethermind.HTTPWebSocket +export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Nethermind +mix test --exclude no_nethermind ``` | Protocol | URL | @@ -83,4 +83,3 @@ def deps do ] end ``` - diff --git a/apps/ethereum_jsonrpc/config/config.exs b/apps/ethereum_jsonrpc/config/config.exs index e85965204da8..e578ffbea78a 100644 --- a/apps/ethereum_jsonrpc/config/config.exs +++ b/apps/ethereum_jsonrpc/config/config.exs @@ -9,10 +9,6 @@ config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator, wait_per_timeout: :timer.seconds(20), max_jitter: :timer.seconds(2) -config :ethereum_jsonrpc, - rpc_transport: if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", do: :http, else: :ipc), - ipc_path: System.get_env("IPC_PATH") - # Add this configuration to add global RPC request throttling. # throttle_rate_limit: 250, # throttle_rolling_window_opts: [ @@ -26,9 +22,6 @@ config :ethereum_jsonrpc, EthereumJSONRPC.Tracer, adapter: SpandexDatadog.Adapter, trace_key: :blockscout -debug_trace_transaction_timeout = System.get_env("ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT", "5s") -config :ethereum_jsonrpc, EthereumJSONRPC.Geth, debug_trace_transaction_timeout: debug_trace_transaction_timeout - config :logger, :ethereum_jsonrpc, # keep synced with `config/config.exs` format: "$dateT$time $metadata[$level] $message\n", diff --git a/apps/ethereum_jsonrpc/config/runtime/test.exs b/apps/ethereum_jsonrpc/config/runtime/test.exs new file mode 100644 index 000000000000..e2043f6c1435 --- /dev/null +++ b/apps/ethereum_jsonrpc/config/runtime/test.exs @@ -0,0 +1,8 @@ +import Config + +alias EthereumJSONRPC.Variant + +variant = Variant.get() + +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../explorer/config/test") +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../indexer/config/test") diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index ab6399f93061..d3f707497bac 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -4,7 +4,7 @@ defmodule EthereumJSONRPC do ## Configuration - Configuration for parity URLs can be provided with the following mix config: + Configuration for Nethermind URLs can be provided with the following mix config: config :ethereum_jsonrpc, url: "http://localhost:8545", @@ -184,7 +184,18 @@ defmodule EthereumJSONRPC do ) :: {:ok, FetchedBalances.t()} | {:error, reason :: term} def fetch_balances(params_list, json_rpc_named_arguments) when is_list(params_list) and is_list(json_rpc_named_arguments) do - id_to_params = id_to_params(params_list) + filtered_params = + if Application.get_env(:ethereum_jsonrpc, :disable_archive_balances?) do + params_list + |> Enum.filter(fn + %{block_quantity: "latest"} -> true + _ -> false + end) + else + params_list + end + + id_to_params = id_to_params(filtered_params) with {:ok, responses} <- id_to_params @@ -453,7 +464,6 @@ defmodule EthereumJSONRPC do end # We can only depend on implementations supporting 64-bit integers: - # * Parity only supports u64 (https://github.com/paritytech/jsonrpc-core/blob/f2c61edb817e344d92ab3baf872fa77d1602430a/src/id.rs#L13) # * Ganache only supports u32 (https://github.com/trufflesuite/ganache-core/issues/190) def unique_request_id do <> = :crypto.strong_rand_bytes(4) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex index 29707f0a3326..67b38e85c8dc 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex @@ -3,11 +3,10 @@ defmodule EthereumJSONRPC.Besu do @moduledoc """ Ethereum JSONRPC methods that are only supported by [Besu](https://besu.hyperledger.org/en/stable/Reference/API-Methods). """ - require Logger - import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] + import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2] - alias EthereumJSONRPC.Besu.{FetchedBeneficiaries, Traces} - alias EthereumJSONRPC.{Transaction, Transactions} + alias EthereumJSONRPC.Besu.Traces + alias EthereumJSONRPC.{FetchedBeneficiaries, PendingTransaction, TraceReplayBlockTransactions, Transaction} @behaviour EthereumJSONRPC.Variant @@ -35,48 +34,12 @@ defmodule EthereumJSONRPC.Besu do """ @impl EthereumJSONRPC.Variant def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do - id_to_params = id_to_params(block_numbers) - - with {:ok, responses} <- - id_to_params - |> trace_replay_block_transactions_requests() - |> json_rpc(json_rpc_named_arguments) do - trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params) - end + TraceReplayBlockTransactions.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments, Traces) end @impl EthereumJSONRPC.Variant def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do - id_to_params = id_to_params(transactions_params) - - trace_replay_transaction_response = - id_to_params - |> trace_replay_transaction_requests() - |> json_rpc(json_rpc_named_arguments) - - case trace_replay_transaction_response do - {:ok, responses} -> - case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) do - {:ok, [first_trace]} -> - %{block_hash: block_hash} = - transactions_params - |> Enum.at(0) - - {:ok, - [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]} - - {:error, error} -> - Logger.error(inspect(error)) - {:error, error} - end - - {:error, :econnrefused} -> - {:error, :econnrefused} - - {:error, [error]} -> - Logger.error(inspect(error)) - {:error, error} - end + TraceReplayBlockTransactions.fetch_first_trace(transactions_params, json_rpc_named_arguments, Traces) end @doc """ @@ -89,217 +52,10 @@ defmodule EthereumJSONRPC.Besu do @spec fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) :: {:ok, [Transaction.params()]} | {:error, reason :: term} def fetch_pending_transactions(json_rpc_named_arguments) do - with {:ok, transactions} <- - %{id: 1, method: "txpool_besuTransactions", params: []} - |> request() - |> json_rpc(json_rpc_named_arguments) do - transactions_params = - transactions - |> Transactions.to_elixir() - |> Transactions.elixir_to_params() - - {:ok, transactions_params} - end + PendingTransaction.fetch_pending_transactions_besu(json_rpc_named_arguments) end defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)}) end - - defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params) - when is_list(responses) and is_map(id_to_params) do - with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do - params = - traces - |> Traces.to_elixir() - |> Traces.elixir_to_params() - - {:ok, params} - end - end - - defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params) - when is_list(responses) and is_map(id_to_params) do - responses - |> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params)) - |> Enum.reduce( - {:ok, []}, - fn - {:ok, traces}, {:ok, acc_traces_list} -> - {:ok, [traces | acc_traces_list]} - - {:ok, _}, {:error, _} = acc_error -> - acc_error - - {:error, reason}, {:ok, _} -> - {:error, [reason]} - - {:error, reason}, {:error, acc_reason} -> - {:error, [reason | acc_reason]} - end - ) - |> case do - {:ok, traces_list} -> - traces = - traces_list - |> Enum.reverse() - |> List.flatten() - - {:ok, traces} - - {:error, reverse_reasons} -> - reasons = Enum.reverse(reverse_reasons) - {:error, reasons} - end - end - - defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params) - when is_list(results) and is_map(id_to_params) do - block_number = Map.fetch!(id_to_params, id) - - annotated_traces = - results - |> Stream.with_index() - |> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} -> - traces - |> Stream.with_index() - |> Enum.map(fn {trace, index} -> - Map.merge(trace, %{ - "blockNumber" => block_number, - "transactionHash" => transaction_hash, - "transactionIndex" => transaction_index, - "index" => index - }) - end) - end) - - {:ok, annotated_traces} - end - - defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params) - when is_map(id_to_params) do - block_number = Map.fetch!(id_to_params, id) - - annotated_error = - Map.put(error, :data, %{ - "blockNumber" => block_number - }) - - {:error, annotated_error} - end - - defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do - Enum.map(id_to_params, fn {id, block_number} -> - trace_replay_block_transactions_request(%{id: id, block_number: block_number}) - end) - end - - defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do - request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]}) - end - - def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) - when is_list(responses) and is_map(id_to_params) do - with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do - params = - traces - |> Traces.to_elixir() - |> Traces.elixir_to_params() - - {:ok, params} - end - end - - defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params) - when is_list(responses) and is_map(id_to_params) do - responses - |> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params)) - |> Enum.reduce( - {:ok, []}, - fn - {:ok, traces}, {:ok, acc_traces_list} -> - {:ok, [traces | acc_traces_list]} - - {:ok, _}, {:error, _} = acc_error -> - acc_error - - {:error, reason}, {:ok, _} -> - {:error, [reason]} - - {:error, reason}, {:error, acc_reason} -> - {:error, [reason | acc_reason]} - end - ) - |> case do - {:ok, traces_list} -> - traces = - traces_list - |> Enum.reverse() - |> List.flatten() - - {:ok, traces} - - {:error, reverse_reasons} -> - reasons = Enum.reverse(reverse_reasons) - {:error, reasons} - end - end - - defp trace_replay_transaction_response_to_first_trace(%{id: id, result: %{"trace" => traces}}, id_to_params) - when is_list(traces) and is_map(id_to_params) do - %{ - block_hash: block_hash, - block_number: block_number, - hash_data: transaction_hash, - transaction_index: transaction_index - } = Map.fetch!(id_to_params, id) - - first_trace = - traces - |> Stream.with_index() - |> Enum.map(fn {trace, index} -> - Map.merge(trace, %{ - "blockHash" => block_hash, - "blockNumber" => block_number, - "index" => index, - "transactionIndex" => transaction_index, - "transactionHash" => transaction_hash - }) - end) - |> Enum.filter(fn trace -> - Map.get(trace, "index") == 0 - end) - - {:ok, first_trace} - end - - defp trace_replay_transaction_response_to_first_trace(%{id: id, error: error}, id_to_params) - when is_map(id_to_params) do - %{ - block_hash: block_hash, - block_number: block_number, - hash_data: transaction_hash, - transaction_index: transaction_index - } = Map.fetch!(id_to_params, id) - - annotated_error = - Map.put(error, :data, %{ - "blockHash" => block_hash, - "blockNumber" => block_number, - "transactionIndex" => transaction_index, - "transactionHash" => transaction_hash - }) - - {:error, annotated_error} - end - - defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do - Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} -> - trace_replay_transaction_request(%{id: id, hash_data: hash_data}) - end) - end - - defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do - request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]}) - end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex deleted file mode 100644 index 0cc22c19f8be..000000000000 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex +++ /dev/null @@ -1,178 +0,0 @@ -defmodule EthereumJSONRPC.Besu.FetchedBeneficiaries do - @moduledoc """ - Beneficiaries and errors from batch requests to `trace_block`. - """ - - import EthereumJSONRPC, only: [quantity_to_integer: 1] - - @doc """ - Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`. - - responses - List with trace_block responses - id_to_params - Maps request id to query params - - ## Examples - iex> EthereumJSONRPC.Besu.FetchedBeneficiaries.from_responses( - ...> [ - ...> %{ - ...> id: 0, - ...> result: [ - ...> %{ - ...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"}, - ...> "blockHash" => "0xFFF", - ...> "blockNumber" => 12, - ...> "result" => nil, - ...> "subtraces" => 0, - ...> "traceAddress" => [], - ...> "transactionHash" => nil, - ...> "transactionPosition" => nil, - ...> "type" => "reward" - ...> }, - ...> %{ - ...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"}, - ...> "blockHash" => "0xFFF", - ...> "blockNumber" => 12, - ...> "result" => nil, - ...> "subtraces" => 0, - ...> "traceAddress" => [], - ...> "transactionHash" => nil, - ...> "transactionPosition" => nil, - ...> "type" => "reward" - ...> } - ...> ] - ...> } - ...> ], - ...> %{0 => %{block_quantity: "0xC"}} - ...> ) - %EthereumJSONRPC.FetchedBeneficiaries{ - errors: [], - params_set: #MapSet<[ - %{ - address_hash: "0x1", - address_type: :validator, - block_hash: "0xFFF", - block_number: 12, - reward: "0x0" - }, - %{ - address_hash: "0x2", - address_type: :emission_funds, - block_hash: "0xFFF", - block_number: 12, - reward: "0x0" - } - ]> - } - """ - def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do - responses - |> Enum.map(&response_to_params_set(&1, id_to_params)) - |> Enum.reduce( - %EthereumJSONRPC.FetchedBeneficiaries{}, - fn - {:ok, params_set}, %EthereumJSONRPC.FetchedBeneficiaries{params_set: acc_params_set} = acc -> - %EthereumJSONRPC.FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)} - - {:error, reason}, %EthereumJSONRPC.FetchedBeneficiaries{errors: errors} = acc -> - %EthereumJSONRPC.FetchedBeneficiaries{acc | errors: [reason | errors]} - end - ) - end - - @doc """ - `trace_block` requests for `id_to_params`. - """ - def requests(id_to_params) when is_map(id_to_params) do - Enum.map(id_to_params, fn {id, %{block_quantity: block_quantity}} -> - request(%{id: id, block_quantity: block_quantity}) - end) - end - - @spec response_to_params_set(%{id: id, result: nil}, %{id => %{block_quantity: block_quantity}}) :: - {:error, %{code: 404, message: String.t(), data: %{block_quantity: block_quantity}}} - when id: non_neg_integer(), block_quantity: String.t() - defp response_to_params_set(%{id: id, result: nil}, id_to_params) when is_map(id_to_params) do - %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) - - {:error, %{code: 404, message: "Not Found", data: %{block_quantity: block_quantity}}} - end - - @spec response_to_params_set(%{id: id, result: list(map())}, %{id => %{block_quantity: block_quantity}}) :: - {:ok, MapSet.t(EthereumJSONRPC.FetchedBeneficiary.params())} - when id: non_neg_integer(), block_quantity: String.t() - defp response_to_params_set(%{id: id, result: traces}, id_to_params) when is_list(traces) and is_map(id_to_params) do - %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) - block_number = quantity_to_integer(block_quantity) - params_set = traces_to_params_set(traces, block_number) - - {:ok, params_set} - end - - @spec response_to_params_set(%{id: id, error: %{code: code, message: message}}, %{ - id => %{block_quantity: block_quantity} - }) :: {:error, %{code: code, message: message, data: %{block_quantity: block_quantity}}} - when id: non_neg_integer(), code: integer(), message: String.t(), block_quantity: String.t() - defp response_to_params_set(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do - %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) - - annotated_error = Map.put(error, :data, %{block_quantity: block_quantity}) - - {:error, annotated_error} - end - - defp request(%{id: id, block_quantity: block_quantity}) when is_integer(id) and is_binary(block_quantity) do - EthereumJSONRPC.request(%{id: id, method: "trace_block", params: [block_quantity]}) - end - - defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do - traces - |> Stream.filter(&(&1["type"] == "reward")) - |> Stream.with_index() - |> Enum.reduce(MapSet.new(), fn {trace, index}, acc -> - MapSet.union(acc, trace_to_params_set(trace, block_number, index)) - end) - end - - defp trace_to_params_set( - %{ - "action" => %{ - "rewardType" => reward_type, - "author" => address_hash_data, - "value" => reward_value - }, - "blockHash" => block_hash, - "blockNumber" => block_number - }, - block_number, - index - ) - when is_integer(block_number) and reward_type in ~w(block external uncle) do - MapSet.new([ - %{ - address_hash: address_hash_data, - block_hash: block_hash, - block_number: block_number, - reward: reward_value, - address_type: get_address_type(reward_type, index) - } - ]) - end - - # Beneficiary's address type will depend on the responses' action.rewardType, - # which will vary depending on which network is being indexed - # - # On POA networks, rewardType will always be external and the type of the address being - # rewarded will depend on its position. - # First address will always be the validator's while the second will be the EmissionsFunds address - # - # On PoW networks, like Ethereum, the reward type will already specify the type for the - # address being rewarded - # The rewardType "block" will show the reward for the consensus block validator - # The rewardType "uncle" will show reward for validating an uncle block - defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator - defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds - defp get_address_type(reward_type, index) when reward_type == "external" and index >= 2, do: :validator - defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator - defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle - defp get_address_type(reward_type, _index) when reward_type == "emptyStep", do: :validator -end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/trace.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/trace.ex index a4a98d8d427b..e06c9d8da3a5 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/trace.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/trace.ex @@ -1,15 +1,14 @@ defmodule EthereumJSONRPC.Besu.Trace do @moduledoc """ Trace returned by - [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an - extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). + [`trace_replayTransaction`](https://openethereum.github.io/JSONRPC-trace-module#trace_replaytransaction). """ - alias EthereumJSONRPC.Parity.Trace, as: ParityTrace - alias EthereumJSONRPC.Parity.Trace.{Action, Result} + alias EthereumJSONRPC.Nethermind.Trace, as: NethermindTrace + alias EthereumJSONRPC.Nethermind.Trace.{Action, Result} def elixir_to_params(elixir) do - ParityTrace.elixir_to_params(elixir) + NethermindTrace.elixir_to_params(elixir) end def to_elixir(%{"blockNumber" => _, "index" => _, "transactionHash" => _, "transactionIndex" => _} = trace) @@ -18,7 +17,7 @@ defmodule EthereumJSONRPC.Besu.Trace do end def to_elixir(trace) do - ParityTrace.to_elixir(trace) + NethermindTrace.to_elixir(trace) end # subtraces is an actual integer in JSON and not hex-encoded diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/traces.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/traces.ex index dfa2d3be85b7..780d3af22cab 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/traces.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/traces.ex @@ -1,8 +1,7 @@ defmodule EthereumJSONRPC.Besu.Traces do @moduledoc """ Trace returned by - [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an - extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). + [`trace_replayTransaction`](https://openethereum.github.io/JSONRPC-trace-module#trace_replaytransaction). """ alias EthereumJSONRPC.Besu.Trace diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex index 1c9d57c4933f..fa4a9b83c990 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex @@ -1,6 +1,6 @@ defmodule EthereumJSONRPC.Block.ByTag do @moduledoc """ - Block format returned by [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) + Block format returned by [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash) when used with a semantic tag name instead of a number. """ @@ -16,8 +16,6 @@ defmodule EthereumJSONRPC.Block.ByTag do {:ok, quantity_to_integer(quantity)} end - # https://github.com/paritytech/parity-ethereum/pull/8281 fixed - # https://github.com/paritytech/parity-ethereum/issues/8028 def number_from_result({:ok, nil}), do: {:error, :not_found} def number_from_result({:error, %{"code" => -32602}}), do: {:error, :invalid_tag} diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex index 448d18350f7b..334643ab07e8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex @@ -219,6 +219,8 @@ defmodule EthereumJSONRPC.Contract do end end + defp format_error(nil), do: {:error, ""} + defp format_error(message) when is_binary(message) do {:error, message} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex index 034c12de67ad..221c082b0218 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex @@ -12,7 +12,7 @@ defmodule EthereumJSONRPC.Encoder do This is what is expected on the Json RPC data parameter. """ @spec encode_function_call(ABI.FunctionSelector.t(), [term()]) :: String.t() - def encode_function_call(function_selector, args) do + def encode_function_call(function_selector, args) when is_list(args) do parsed_args = parse_args(args) encoded_args = @@ -23,6 +23,8 @@ defmodule EthereumJSONRPC.Encoder do "0x" <> encoded_args end + def encode_function_call(function_selector, args), do: encode_function_call(function_selector, [args]) + defp parse_args(args) when is_list(args) do args |> Enum.map(&parse_args/1) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erigon.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erigon.ex new file mode 100644 index 000000000000..8173a98df2a5 --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erigon.ex @@ -0,0 +1,56 @@ +# credo:disable-for-this-file +defmodule EthereumJSONRPC.Erigon do + @moduledoc """ + Ethereum JSONRPC methods that are only supported by Erigon. + """ + import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2] + + alias EthereumJSONRPC.Nethermind.Traces + alias EthereumJSONRPC.{FetchedBeneficiaries, PendingTransaction, TraceReplayBlockTransactions} + + @behaviour EthereumJSONRPC.Variant + + @impl EthereumJSONRPC.Variant + def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) + when is_list(block_numbers) and is_list(json_rpc_named_arguments) do + id_to_params = + block_numbers + |> block_numbers_to_params_list() + |> id_to_params() + + with {:ok, responses} <- + id_to_params + |> FetchedBeneficiaries.requests() + |> json_rpc(json_rpc_named_arguments) do + {:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)} + end + end + + @impl EthereumJSONRPC.Variant + def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore + + @doc """ + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Erigon trace URL. + """ + @impl EthereumJSONRPC.Variant + def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do + TraceReplayBlockTransactions.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments, Traces) + end + + @impl EthereumJSONRPC.Variant + def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do + TraceReplayBlockTransactions.fetch_first_trace(transactions_params, json_rpc_named_arguments, Traces) + end + + @doc """ + Fetches the pending transactions from the Geth node. + """ + @impl EthereumJSONRPC.Variant + def fetch_pending_transactions(json_rpc_named_arguments) do + PendingTransaction.fetch_pending_transactions_geth(json_rpc_named_arguments) + end + + defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do + Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)}) + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex index 8dcf2cd8b677..4e55e6e2a249 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex @@ -1,8 +1,10 @@ defmodule EthereumJSONRPC.FetchedBeneficiaries do @moduledoc """ - Balance params and errors from a batch request to fetch beneficiaries. + Beneficiaries and errors from batch requests to `trace_block`. """ + import EthereumJSONRPC, only: [quantity_to_integer: 1] + alias EthereumJSONRPC.FetchedBeneficiary defstruct params_set: MapSet.new(), @@ -13,4 +15,181 @@ defmodule EthereumJSONRPC.FetchedBeneficiaries do * `errors` - all the errors from requests that failed in the batch. """ @type t :: %__MODULE__{params_set: MapSet.t(FetchedBeneficiary.params()), errors: [FetchedBeneficiary.error()]} + + @doc """ + Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`. + + responses - List with trace_block responses + id_to_params - Maps request id to query params + + ## Examples + iex> EthereumJSONRPC.FetchedBeneficiaries.from_responses( + ...> [ + ...> %{ + ...> id: 0, + ...> result: [ + ...> %{ + ...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"}, + ...> "blockHash" => "0xFFF", + ...> "blockNumber" => 12, + ...> "result" => nil, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => nil, + ...> "transactionPosition" => nil, + ...> "type" => "reward" + ...> }, + ...> %{ + ...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"}, + ...> "blockHash" => "0xFFF", + ...> "blockNumber" => 12, + ...> "result" => nil, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => nil, + ...> "transactionPosition" => nil, + ...> "type" => "reward" + ...> } + ...> ] + ...> } + ...> ], + ...> %{0 => %{block_quantity: "0xC"}} + ...> ) + %EthereumJSONRPC.FetchedBeneficiaries{ + errors: [], + params_set: #MapSet<[ + %{ + address_hash: "0x1", + address_type: :validator, + block_hash: "0xFFF", + block_number: 12, + reward: "0x0" + }, + %{ + address_hash: "0x2", + address_type: :emission_funds, + block_hash: "0xFFF", + block_number: 12, + reward: "0x0" + } + ]> + } + """ + def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do + responses + |> Enum.map(&response_to_params_set(&1, id_to_params)) + |> Enum.reduce( + %EthereumJSONRPC.FetchedBeneficiaries{}, + fn + {:ok, params_set}, %EthereumJSONRPC.FetchedBeneficiaries{params_set: acc_params_set} = acc -> + %EthereumJSONRPC.FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)} + + {:error, reason}, %EthereumJSONRPC.FetchedBeneficiaries{errors: errors} = acc -> + %EthereumJSONRPC.FetchedBeneficiaries{acc | errors: [reason | errors]} + end + ) + end + + @doc """ + `trace_block` requests for `id_to_params`. + """ + def requests(id_to_params) when is_map(id_to_params) do + id_to_params + |> Enum.map(fn {id, %{block_quantity: block_quantity}} -> + request(%{id: id, block_quantity: block_quantity}) + end) + |> Enum.filter(&(!is_nil(&1))) + end + + @spec response_to_params_set(%{id: id, result: nil}, %{id => %{block_quantity: block_quantity}}) :: + {:error, %{code: 404, message: String.t(), data: %{block_quantity: block_quantity}}} + when id: non_neg_integer(), block_quantity: String.t() + defp response_to_params_set(%{id: id, result: nil}, id_to_params) when is_map(id_to_params) do + %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) + + {:error, %{code: 404, message: "Not Found", data: %{block_quantity: block_quantity}}} + end + + @spec response_to_params_set(%{id: id, result: list(map())}, %{id => %{block_quantity: block_quantity}}) :: + {:ok, MapSet.t(EthereumJSONRPC.FetchedBeneficiary.params())} + when id: non_neg_integer(), block_quantity: String.t() + defp response_to_params_set(%{id: id, result: traces}, id_to_params) when is_list(traces) and is_map(id_to_params) do + %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) + block_number = quantity_to_integer(block_quantity) + params_set = traces_to_params_set(traces, block_number) + + {:ok, params_set} + end + + @spec response_to_params_set(%{id: id, error: %{code: code, message: message}}, %{ + id => %{block_quantity: block_quantity} + }) :: {:error, %{code: code, message: message, data: %{block_quantity: block_quantity}}} + when id: non_neg_integer(), code: integer(), message: String.t(), block_quantity: String.t() + defp response_to_params_set(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do + %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) + + annotated_error = Map.put(error, :data, %{block_quantity: block_quantity}) + + {:error, annotated_error} + end + + defp request(%{id: id, block_quantity: block_quantity}) when is_integer(id) and is_binary(block_quantity) do + if block_quantity == "0x0" do + nil + else + EthereumJSONRPC.request(%{id: id, method: "trace_block", params: [block_quantity]}) + end + end + + defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do + traces + |> Stream.filter(&(&1["type"] == "reward")) + |> Stream.with_index() + |> Enum.reduce(MapSet.new(), fn {trace, index}, acc -> + MapSet.union(acc, trace_to_params_set(trace, block_number, index)) + end) + end + + defp trace_to_params_set( + %{ + "action" => %{ + "rewardType" => reward_type, + "author" => address_hash_data, + "value" => reward_value + }, + "blockHash" => block_hash, + "blockNumber" => block_number + }, + block_number, + index + ) + when is_integer(block_number) and reward_type in ~w(block external uncle) do + MapSet.new([ + %{ + address_hash: address_hash_data, + block_hash: block_hash, + block_number: block_number, + reward: reward_value, + address_type: get_address_type(reward_type, index) + } + ]) + end + + # Beneficiary's address type will depend on the responses' action.rewardType, + # which will vary depending on which network is being indexed + # + # On POA networks, rewardType will always be external and the type of the address being + # rewarded will depend on its position. + # First address will always be the validator's while the second will be the EmissionsFunds address + # + # On PoW networks, like Ethereum, the reward type will already specify the type for the + # address being rewarded + # The rewardType "block" will show the reward for the consensus block validator + # The rewardType "uncle" will show reward for validating an uncle block + defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator + defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds + defp get_address_type(reward_type, index) when reward_type == "external" and index >= 2, do: :validator + defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator + defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle + defp get_address_type(reward_type, _index) when reward_type == "emptyStep", do: :validator end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index b067ed1f1cc8..c14f21603500 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -5,7 +5,7 @@ defmodule EthereumJSONRPC.Geth do import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] - alias EthereumJSONRPC.{FetchedBalance, FetchedCode, Transactions} + alias EthereumJSONRPC.{FetchedBalance, FetchedCode, PendingTransaction} alias EthereumJSONRPC.Geth.{Calls, Tracer} @behaviour EthereumJSONRPC.Variant @@ -56,26 +56,7 @@ defmodule EthereumJSONRPC.Geth do """ @impl EthereumJSONRPC.Variant def fetch_pending_transactions(json_rpc_named_arguments) do - with {:ok, transaction_data} <- - %{id: 1, method: "txpool_content", params: []} |> request() |> json_rpc(json_rpc_named_arguments) do - transactions_params = - transaction_data["pending"] - |> Enum.flat_map(fn {_address, nonce_transactions_map} -> - nonce_transactions_map - |> Enum.map(fn {_nonce, transaction} -> - transaction - end) - end) - |> Transactions.to_elixir() - |> Transactions.elixir_to_params() - |> Enum.map(fn params -> - # txpool_content always returns transaction with 0x0000000000000000000000000000000000000000000000000000000000000000 value in block hash and index is null. - # https://github.com/ethereum/go-ethereum/issues/19897 - %{params | block_hash: nil, index: nil} - end) - - {:ok, transactions_params} - end + PendingTransaction.fetch_pending_transactions_geth(json_rpc_named_arguments) end defp debug_trace_transaction_requests(id_to_params) when is_map(id_to_params) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex index c83b1573242b..dfdbb965fcd4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex @@ -254,7 +254,8 @@ defmodule EthereumJSONRPC.Geth.Call do gas_used: 1040, input: "0x0f370699", output: "0x", - value: 0 + value: 0, + error: nil } A selfdestruct destroys the calling contract and sends any left over balance to the to address. @@ -416,7 +417,8 @@ defmodule EthereumJSONRPC.Geth.Call do gas_used: gas_used, input: input, output: params["output"], - value: value + value: value, + error: params["error"] } end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index b8222df2d13e..dc66006f9e80 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -3,7 +3,7 @@ defmodule EthereumJSONRPC.HTTP do JSONRPC over HTTP """ - alias EthereumJSONRPC.Transport + alias EthereumJSONRPC.{DecodeError, Transport} require Logger @@ -114,7 +114,17 @@ defmodule EthereumJSONRPC.HTTP do {:error, {:bad_gateway, request_url}} _ -> - raise EthereumJSONRPC.DecodeError, named_arguments + named_arguments + |> DecodeError.exception() + |> DecodeError.message() + |> Logger.error() + + request_url = + named_arguments + |> Keyword.fetch!(:request) + |> Keyword.fetch!(:url) + + {:error, {:bad_response, request_url}} end end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex index 52cdae96bd90..03ed4631ce6b 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex @@ -135,7 +135,7 @@ defmodule EthereumJSONRPC.Log do "type" => "mined" } - Geth and Parity >= 1.11.4 includes a `"removed"` key + Geth includes a `"removed"` key iex> EthereumJSONRPC.Log.to_elixir( ...> %{ @@ -171,8 +171,9 @@ defmodule EthereumJSONRPC.Log do Enum.into(log, %{}, &entry_to_elixir/1) end - defp entry_to_elixir({key, _} = entry) when key in ~w(address blockHash data removed topics transactionHash type), - do: entry + defp entry_to_elixir({key, _} = entry) + when key in ~w(address blockHash data removed topics transactionHash type timestamp), + do: entry defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do if is_nil(quantity) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind.ex new file mode 100644 index 000000000000..1359e97f39bd --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind.ex @@ -0,0 +1,65 @@ +# credo:disable-for-this-file +defmodule EthereumJSONRPC.Nethermind do + @moduledoc """ + Ethereum JSONRPC methods that are only supported by Nethermind. + """ + import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2] + + alias EthereumJSONRPC.Nethermind.Traces + alias EthereumJSONRPC.{FetchedBeneficiaries, PendingTransaction, TraceReplayBlockTransactions, Transaction} + + @behaviour EthereumJSONRPC.Variant + + @impl EthereumJSONRPC.Variant + def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) + when is_list(block_numbers) and is_list(json_rpc_named_arguments) do + id_to_params = + block_numbers + |> block_numbers_to_params_list() + |> id_to_params() + + with {:ok, responses} <- + id_to_params + |> FetchedBeneficiaries.requests() + |> json_rpc(json_rpc_named_arguments) do + {:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)} + end + end + + @impl EthereumJSONRPC.Variant + def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore + + @doc """ + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Nethermind trace URL. + """ + @impl EthereumJSONRPC.Variant + def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do + TraceReplayBlockTransactions.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments, Traces) + end + + @impl EthereumJSONRPC.Variant + def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do + TraceReplayBlockTransactions.fetch_first_trace(transactions_params, json_rpc_named_arguments, Traces) + end + + @doc """ + Fetches the pending transactions from the Nethermind node. + + *NOTE*: The pending transactions are local to the node that is contacted and may not be consistent across nodes based + on the transactions that each node has seen and how each node prioritizes collating transactions into the next block. + """ + @impl EthereumJSONRPC.Variant + @spec fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) :: + {:ok, [Transaction.params()]} | {:error, reason :: term} + def fetch_pending_transactions(json_rpc_named_arguments) do + if Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.PendingTransaction)[:type] == "geth" do + PendingTransaction.fetch_pending_transactions_geth(json_rpc_named_arguments) + else + PendingTransaction.fetch_pending_transactions_parity(json_rpc_named_arguments) + end + end + + defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do + Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)}) + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace.ex similarity index 97% rename from apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace.ex index dc17f3848b82..047a4d6bbd8f 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace.ex @@ -1,16 +1,15 @@ -defmodule EthereumJSONRPC.Parity.Trace do +defmodule EthereumJSONRPC.Nethermind.Trace do @moduledoc """ Trace returned by - [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an - extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). + [`trace_replayTransaction`](https://openethereum.github.io/JSONRPC-trace-module#trace_replaytransaction). """ - alias EthereumJSONRPC.Parity.Trace.{Action, Result} + alias EthereumJSONRPC.Nethermind.Trace.{Action, Result} @doc """ Create type traces are generated when a contract is created. - iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Nethermind.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", @@ -50,7 +49,7 @@ defmodule EthereumJSONRPC.Parity.Trace do A create can fail due to a Bad Instruction in the `init` that is meant to form the `code` of the contract - iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Nethermind.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "from" => "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", @@ -84,7 +83,7 @@ defmodule EthereumJSONRPC.Parity.Trace do Call type traces are generated when a method is called. Calls are further divided by call type. - iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Nethermind.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "callType" => "call", @@ -126,7 +125,7 @@ defmodule EthereumJSONRPC.Parity.Trace do Calls can error and be reverted - iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Nethermind.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "callType" => "call", @@ -171,7 +170,7 @@ defmodule EthereumJSONRPC.Parity.Trace do | `"balance"` | `:value` | | `"refundAddress"` | `:to_address_hash` | - iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Nethermind.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", @@ -291,7 +290,7 @@ defmodule EthereumJSONRPC.Parity.Trace do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> EthereumJSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Nethermind.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", @@ -334,10 +333,10 @@ defmodule EthereumJSONRPC.Parity.Trace do "type" => "create" } - The caller must put `"blockNumber"`, `"index"`, and `"transactionHash"` into the incoming map, as Parity itself does + The caller must put `"blockNumber"`, `"index"`, and `"transactionHash"` into the incoming map, as Nethermind itself does not include that information, but it is needed to locate the trace in history and update addresses fully. - iex> EthereumJSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Nethermind.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", @@ -359,9 +358,9 @@ defmodule EthereumJSONRPC.Parity.Trace do ** (ArgumentError) Caller must `Map.put/2` `"blockNumber"`, `"index"`, `"transactionHash"` and `"transactionIndex"` in trace `"suicide"` `"type"` traces are different in that they have a `nil` `"result"`. This is because the `"result"` key - is used to indicate success from Parity. + is used to indicate success from Nethermind. - iex> EthereumJSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Nethermind.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", @@ -396,7 +395,7 @@ defmodule EthereumJSONRPC.Parity.Trace do A call type trace can error and be reverted. - iex> EthereumJSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Nethermind.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "callType" => "call", diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/action.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace/action.ex similarity index 94% rename from apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/action.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace/action.ex index 4785c0d0cf56..ee5d571653b2 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/action.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace/action.ex @@ -1,6 +1,6 @@ -defmodule EthereumJSONRPC.Parity.Trace.Action do +defmodule EthereumJSONRPC.Nethermind.Trace.Action do @moduledoc """ - The action that was performed in a `t:EthereumJSONRPC.Parity.Trace.t/0` + The action that was performed in a `t:EthereumJSONRPC.Nethermind.Trace.t/0` """ import EthereumJSONRPC, only: [quantity_to_integer: 1] @@ -8,7 +8,7 @@ defmodule EthereumJSONRPC.Parity.Trace.Action do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir( + iex> EthereumJSONRPC.Nethermind.Trace.Action.to_elixir( ...> %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "gas" => "0x462534", @@ -26,7 +26,7 @@ defmodule EthereumJSONRPC.Parity.Trace.Action do For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address"` and `"refundAddress"` `t:EthereumJSONRPC.hash/0` pass through. - iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir( + iex> EthereumJSONRPC.Nethermind.Trace.Action.to_elixir( ...> %{ ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", ...> "balance" => "0x0", diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/result.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace/result.ex similarity index 92% rename from apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/result.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace/result.ex index 23ef64243676..c3ea279a8c6c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/result.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace/result.ex @@ -1,6 +1,6 @@ -defmodule EthereumJSONRPC.Parity.Trace.Result do +defmodule EthereumJSONRPC.Nethermind.Trace.Result do @moduledoc """ - The result of performing the `t:EthereumJSONRPC.Parity.Action.t/0` in a `t:EthereumJSONRPC.Parity.Trace.t/0`. + The result of performing the `t:EthereumJSONRPC.Nethermind.Action.t/0` in a `t:EthereumJSONRPC.Nethermind.Trace.t/0`. """ import EthereumJSONRPC, only: [quantity_to_integer: 1] @@ -8,7 +8,7 @@ defmodule EthereumJSONRPC.Parity.Trace.Result do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir( + iex> EthereumJSONRPC.Nethermind.Trace.Result.to_elixir( ...> %{ ...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", ...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", @@ -23,7 +23,7 @@ defmodule EthereumJSONRPC.Parity.Trace.Result do `nil` results can occur for suicide type traces. - iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir(nil) + iex> EthereumJSONRPC.Nethermind.Trace.Result.to_elixir(nil) nil """ diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/traces.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/traces.ex new file mode 100644 index 000000000000..1386eb5f84da --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/traces.ex @@ -0,0 +1,16 @@ +defmodule EthereumJSONRPC.Nethermind.Traces do + @moduledoc """ + Trace returned by + [`trace_replayTransaction`](https://openethereum.github.io/JSONRPC-trace-module#trace_replaytransaction). + """ + + alias EthereumJSONRPC.Nethermind.Trace + + def elixir_to_params(elixir) when is_list(elixir) do + Enum.map(elixir, &Trace.elixir_to_params/1) + end + + def to_elixir(traces) when is_list(traces) do + Enum.map(traces, &Trace.to_elixir/1) + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex deleted file mode 100644 index 4c8d0ca780df..000000000000 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex +++ /dev/null @@ -1,184 +0,0 @@ -defmodule EthereumJSONRPC.Parity.FetchedBeneficiaries do - @moduledoc """ - Beneficiaries and errors from batch requests to `trace_block`. - """ - - import EthereumJSONRPC, only: [quantity_to_integer: 1] - - @doc """ - Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`. - - responses - List with trace_block responses - id_to_params - Maps request id to query params - - ## Examples - iex> EthereumJSONRPC.Parity.FetchedBeneficiaries.from_responses( - ...> [ - ...> %{ - ...> id: 0, - ...> result: [ - ...> %{ - ...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"}, - ...> "blockHash" => "0xFFF", - ...> "blockNumber" => 12, - ...> "result" => nil, - ...> "subtraces" => 0, - ...> "traceAddress" => [], - ...> "transactionHash" => nil, - ...> "transactionPosition" => nil, - ...> "type" => "reward" - ...> }, - ...> %{ - ...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"}, - ...> "blockHash" => "0xFFF", - ...> "blockNumber" => 12, - ...> "result" => nil, - ...> "subtraces" => 0, - ...> "traceAddress" => [], - ...> "transactionHash" => nil, - ...> "transactionPosition" => nil, - ...> "type" => "reward" - ...> } - ...> ] - ...> } - ...> ], - ...> %{0 => %{block_quantity: "0xC"}} - ...> ) - %EthereumJSONRPC.FetchedBeneficiaries{ - errors: [], - params_set: #MapSet<[ - %{ - address_hash: "0x1", - address_type: :validator, - block_hash: "0xFFF", - block_number: 12, - reward: "0x0" - }, - %{ - address_hash: "0x2", - address_type: :emission_funds, - block_hash: "0xFFF", - block_number: 12, - reward: "0x0" - } - ]> - } - """ - def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do - responses - |> Enum.map(&response_to_params_set(&1, id_to_params)) - |> Enum.reduce( - %EthereumJSONRPC.FetchedBeneficiaries{}, - fn - {:ok, params_set}, %EthereumJSONRPC.FetchedBeneficiaries{params_set: acc_params_set} = acc -> - %EthereumJSONRPC.FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)} - - {:error, reason}, %EthereumJSONRPC.FetchedBeneficiaries{errors: errors} = acc -> - %EthereumJSONRPC.FetchedBeneficiaries{acc | errors: [reason | errors]} - end - ) - end - - @doc """ - `trace_block` requests for `id_to_params`. - """ - def requests(id_to_params) when is_map(id_to_params) do - id_to_params - |> Enum.map(fn {id, %{block_quantity: block_quantity}} -> - request(%{id: id, block_quantity: block_quantity}) - end) - |> Enum.filter(&(!is_nil(&1))) - end - - @spec response_to_params_set(%{id: id, result: nil}, %{id => %{block_quantity: block_quantity}}) :: - {:error, %{code: 404, message: String.t(), data: %{block_quantity: block_quantity}}} - when id: non_neg_integer(), block_quantity: String.t() - defp response_to_params_set(%{id: id, result: nil}, id_to_params) when is_map(id_to_params) do - %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) - - {:error, %{code: 404, message: "Not Found", data: %{block_quantity: block_quantity}}} - end - - @spec response_to_params_set(%{id: id, result: list(map())}, %{id => %{block_quantity: block_quantity}}) :: - {:ok, MapSet.t(EthereumJSONRPC.FetchedBeneficiary.params())} - when id: non_neg_integer(), block_quantity: String.t() - defp response_to_params_set(%{id: id, result: traces}, id_to_params) when is_list(traces) and is_map(id_to_params) do - %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) - block_number = quantity_to_integer(block_quantity) - params_set = traces_to_params_set(traces, block_number) - - {:ok, params_set} - end - - @spec response_to_params_set(%{id: id, error: %{code: code, message: message}}, %{ - id => %{block_quantity: block_quantity} - }) :: {:error, %{code: code, message: message, data: %{block_quantity: block_quantity}}} - when id: non_neg_integer(), code: integer(), message: String.t(), block_quantity: String.t() - defp response_to_params_set(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do - %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id) - - annotated_error = Map.put(error, :data, %{block_quantity: block_quantity}) - - {:error, annotated_error} - end - - defp request(%{id: id, block_quantity: block_quantity}) when is_integer(id) and is_binary(block_quantity) do - if block_quantity == "0x0" do - nil - else - EthereumJSONRPC.request(%{id: id, method: "trace_block", params: [block_quantity]}) - end - end - - defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do - traces - |> Stream.filter(&(&1["type"] == "reward")) - |> Stream.with_index() - |> Enum.reduce(MapSet.new(), fn {trace, index}, acc -> - MapSet.union(acc, trace_to_params_set(trace, block_number, index)) - end) - end - - defp trace_to_params_set( - %{ - "action" => %{ - "rewardType" => reward_type, - "author" => address_hash_data, - "value" => reward_value - }, - "blockHash" => block_hash, - "blockNumber" => block_number - }, - block_number, - index - ) - when is_integer(block_number) and reward_type in ~w(block external uncle) do - MapSet.new([ - %{ - address_hash: address_hash_data, - block_hash: block_hash, - block_number: block_number, - reward: reward_value, - address_type: get_address_type(reward_type, index) - } - ]) - end - - # Beneficiary's address type will depend on the responses' action.rewardType, - # which will vary depending on which network is being indexed - # - # On POA networks, rewardType will always be external and the type of the address being - # rewarded will depend on its position. - # First address will always be the validator's while the second will be the EmissionsFunds address - # - # On PoW networks, like Ethereum, the reward type will already specify the type for the - # address being rewarded - # The rewardType "block" will show the reward for the consensus block validator - # The rewardType "uncle" will show reward for validating an uncle block - defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator - defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds - defp get_address_type(reward_type, index) when reward_type == "external" and index >= 2, do: :validator - defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator - defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle - defp get_address_type(reward_type, _index) when reward_type == "emptyStep", do: :validator -end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/traces.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/traces.ex deleted file mode 100644 index f18186fd25d6..000000000000 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/traces.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule EthereumJSONRPC.Parity.Traces do - @moduledoc """ - Trace returned by - [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an - extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). - """ - - alias EthereumJSONRPC.Parity.Trace - - def elixir_to_params(elixir) when is_list(elixir) do - Enum.map(elixir, &Trace.elixir_to_params/1) - end - - def to_elixir(traces) when is_list(traces) do - Enum.map(traces, &Trace.to_elixir/1) - end -end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex new file mode 100644 index 000000000000..b2754163f0af --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex @@ -0,0 +1,71 @@ +defmodule EthereumJSONRPC.PendingTransaction do + @moduledoc """ + Defines pending transactions fetching functions + """ + + import EthereumJSONRPC, only: [json_rpc: 2, request: 1] + alias EthereumJSONRPC.{Transaction, Transactions} + + @doc """ + Geth-style fetching of pending transactions (from `txpool_content`) + """ + @spec fetch_pending_transactions_geth(EthereumJSONRPC.json_rpc_named_arguments()) :: + {:ok, [Transaction.params()]} | {:error, reason :: term} + def fetch_pending_transactions_geth(json_rpc_named_arguments) do + with {:ok, transaction_data} <- + %{id: 1, method: "txpool_content", params: []} |> request() |> json_rpc(json_rpc_named_arguments) do + transactions_params = + transaction_data["pending"] + |> Enum.flat_map(fn {_address, nonce_transactions_map} -> + nonce_transactions_map + |> Enum.map(fn {_nonce, transaction} -> + transaction + end) + end) + |> Transactions.to_elixir() + |> Transactions.elixir_to_params() + |> Enum.map(fn params -> + # txpool_content always returns transaction with 0x0000000000000000000000000000000000000000000000000000000000000000 value in block hash and index is null. + # https://github.com/ethereum/go-ethereum/issues/19897 + %{params | block_hash: nil, index: nil} + end) + + {:ok, transactions_params} + end + end + + @doc """ + Đ—Ņ„Đē҈ĐĩĐŊ-style fetching of pending transactions (from `parity_pendingTransactions`) + """ + @spec fetch_pending_transactions_parity(EthereumJSONRPC.json_rpc_named_arguments()) :: + {:ok, [Transaction.params()]} | {:error, reason :: term} + def fetch_pending_transactions_parity(json_rpc_named_arguments) do + with {:ok, transactions} <- + %{id: 1, method: "parity_pendingTransactions", params: []} + |> request() + |> json_rpc(json_rpc_named_arguments) do + transactions_params = + transactions + |> Transactions.to_elixir() + |> Transactions.elixir_to_params() + + {:ok, transactions_params} + end + end + + @spec fetch_pending_transactions_besu(EthereumJSONRPC.json_rpc_named_arguments()) :: + {:ok, [Transaction.params()]} | {:error, reason :: term} + def fetch_pending_transactions_besu(json_rpc_named_arguments) do + with {:ok, transactions} <- + %{id: 1, method: "txpool_besuTransactions", params: []} + |> request() + |> json_rpc(json_rpc_named_arguments) do + transactions_params = + transactions + |> Transactions.to_elixir() + |> Transactions.elixir_to_params() + + {:ok, transactions_params} + end + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 3f90c7977f26..3ecad95d6860 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -280,7 +280,7 @@ defmodule EthereumJSONRPC.Receipt do one when one in ["0x1", "0x01"] -> {:ok, {key, :ok}} - # pre-Byzantium / Ethereum Classic on Parity + # pre-Byzantium nil -> :ignore diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex index bd491f25fd7f..b63e0971a6d3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex @@ -109,7 +109,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do defp trace_request(_, fun), do: fun.() - defp handle_transport_response({:error, {:bad_gateway, _}} = error) do + defp handle_transport_response({:error, {error_type, _}} = error) when error_type in [:bad_gateway, :bad_response] do RollingWindow.inc(table(), @error_key) inc_throttle_table() error diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex similarity index 72% rename from apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex index 8165651ad563..49b395b86b04 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex @@ -1,57 +1,12 @@ -# credo:disable-for-this-file -defmodule EthereumJSONRPC.Parity do +defmodule EthereumJSONRPC.TraceReplayBlockTransactions do @moduledoc """ - Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/). + Methods for processing the data from `trace_replayBlockTransactions` JSON RPC method """ require Logger import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] - alias EthereumJSONRPC.Parity.{FetchedBeneficiaries, Traces} - alias EthereumJSONRPC.{Transaction, Transactions} - - @behaviour EthereumJSONRPC.Variant - - @impl EthereumJSONRPC.Variant - def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) - when is_list(block_numbers) and is_list(json_rpc_named_arguments) do - id_to_params = - block_numbers - |> block_numbers_to_params_list() - |> id_to_params() - - with {:ok, responses} <- - id_to_params - |> FetchedBeneficiaries.requests() - |> json_rpc(json_rpc_named_arguments) do - {:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)} - end - end - - @doc """ - Internal transaction fetching for individual transactions is no longer supported for Parity. - - To signal to the caller that fetching is not supported, `:ignore` is returned. - """ - @impl EthereumJSONRPC.Variant - def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore - - @doc """ - Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. - """ - @impl EthereumJSONRPC.Variant - def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do - id_to_params = id_to_params(block_numbers) - - with {:ok, responses} <- - id_to_params - |> trace_replay_block_transactions_requests() - |> json_rpc(json_rpc_named_arguments) do - trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params) - end - end - - @impl EthereumJSONRPC.Variant - def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do + def fetch_first_trace(transactions_params, json_rpc_named_arguments, traces_module) + when is_list(transactions_params) do id_to_params = id_to_params(transactions_params) trace_replay_transaction_response = @@ -61,7 +16,7 @@ defmodule EthereumJSONRPC.Parity do case trace_replay_transaction_response do {:ok, responses} -> - case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) do + case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params, traces_module) do {:ok, [first_trace]} -> %{block_hash: block_hash} = transactions_params @@ -84,40 +39,25 @@ defmodule EthereumJSONRPC.Parity do end end - @doc """ - Fetches the pending transactions from the Parity node. + def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments, traces_module) + when is_list(block_numbers) do + id_to_params = id_to_params(block_numbers) - *NOTE*: The pending transactions are local to the node that is contacted and may not be consistent across nodes based - on the transactions that each node has seen and how each node prioritizes collating transactions into the next block. - """ - @impl EthereumJSONRPC.Variant - @spec fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) :: - {:ok, [Transaction.params()]} | {:error, reason :: term} - def fetch_pending_transactions(json_rpc_named_arguments) do - with {:ok, transactions} <- - %{id: 1, method: "parity_pendingTransactions", params: []} - |> request() + with {:ok, responses} <- + id_to_params + |> trace_replay_block_transactions_requests() |> json_rpc(json_rpc_named_arguments) do - transactions_params = - transactions - |> Transactions.to_elixir() - |> Transactions.elixir_to_params() - - {:ok, transactions_params} + trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params, traces_module) end end - defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do - Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)}) - end - - defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params) + defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params, traces_module) when is_list(responses) and is_map(id_to_params) do with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do params = traces - |> Traces.to_elixir() - |> Traces.elixir_to_params() + |> traces_module.to_elixir() + |> traces_module.elixir_to_params() {:ok, params} end @@ -203,13 +143,13 @@ defmodule EthereumJSONRPC.Parity do request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]}) end - def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) + def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params, traces_module) when is_list(responses) and is_map(id_to_params) do with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do params = traces - |> Traces.to_elixir() - |> Traces.elixir_to_params() + |> traces_module.to_elixir() + |> traces_module.elixir_to_params() {:ok, params} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index 3ba37558ecdd..21f56cd70622 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -72,7 +72,7 @@ defmodule EthereumJSONRPC.Transaction do } @doc """ - Geth `elixir` can be converted to `params`. Geth does not supply `"publicKey"` or `"standardV"`, unlike Parity. + Geth `elixir` can be converted to `params`. Geth does not supply `"publicKey"` or `"standardV"`, unlike Nethermind. iex> EthereumJSONRPC.Transaction.elixir_to_params( ...> %{ @@ -110,6 +110,47 @@ defmodule EthereumJSONRPC.Transaction do transaction_index: 0 } + Erigon `elixir` from txpool_content method can be converted to `params`. + + iex> EthereumJSONRPC.Transaction.elixir_to_params( + ...> %{ + ...> "blockHash" => nil, + ...> "blockNumber" => nil, + ...> "from" => "0x870006d72c247bc1e90983c71b3234ee01d3c9d9", + ...> "gas" => 182154, + ...> "hash" => "0x8d2cd1fae48ea0d2a20bb74abbfca05c2d805793e1b42fa844bbdd90f2512f39", + ...> "input" => "0x08dc9f4200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000062ad7e5d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000041ea9764027fc630affb1825e91d830d1e4e9b6e701ce6eb531f7262787739466d0fc8ac23d0496ec1386d6727f3958fc147719d12a4a23b3e592fb7f499006e041b00000000000000000000000000000000000000000000000000000000000000", + ...> "maxFeePerGas" => 169648965806, + ...> "maxPriorityFeePerGas" => 0, + ...> "nonce" => 12, + ...> "r" => 54145857155959999983086152958232689936309058616635618472746031287964711695698, + ...> "s" => 15362740689264852081830165187058601082381583930006578342376475668171980574077, + ...> "to" => "0x7a41e410bb784d9875fa14f2d7d2fa825466cdae", + ...> "transactionIndex" => nil, + ...> "type" => 2, + ...> "v" => 0, + ...> "value" => 275000000000000000 + ...> } + ...> ) + %{ + block_hash: nil, + block_number: nil, + from_address_hash: "0x870006d72c247bc1e90983c71b3234ee01d3c9d9", + gas: 182154, + hash: "0x8d2cd1fae48ea0d2a20bb74abbfca05c2d805793e1b42fa844bbdd90f2512f39", + index: nil, + input: "0x08dc9f4200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000062ad7e5d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000041ea9764027fc630affb1825e91d830d1e4e9b6e701ce6eb531f7262787739466d0fc8ac23d0496ec1386d6727f3958fc147719d12a4a23b3e592fb7f499006e041b00000000000000000000000000000000000000000000000000000000000000", + max_fee_per_gas: 169648965806, + max_priority_fee_per_gas: 0, + nonce: 12, + r: 54145857155959999983086152958232689936309058616635618472746031287964711695698, + s: 15362740689264852081830165187058601082381583930006578342376475668171980574077, + to_address_hash: "0x7a41e410bb784d9875fa14f2d7d2fa825466cdae", + transaction_index: nil, + type: 2, + v: 0, + value: 275000000000000000 + } """ @spec elixir_to_params(elixir) :: params @@ -162,6 +203,55 @@ defmodule EthereumJSONRPC.Transaction do end end + # txpool_content method on Erigon node returns tx data + # without gas price + def elixir_to_params( + %{ + "blockHash" => block_hash, + "blockNumber" => block_number, + "from" => from_address_hash, + "gas" => gas, + "hash" => hash, + "input" => input, + "nonce" => nonce, + "r" => r, + "s" => s, + "to" => to_address_hash, + "transactionIndex" => index, + "v" => v, + "value" => value, + "type" => type, + "maxPriorityFeePerGas" => max_priority_fee_per_gas, + "maxFeePerGas" => max_fee_per_gas + } = transaction + ) do + result = %{ + block_hash: block_hash, + block_number: block_number, + from_address_hash: from_address_hash, + gas: gas, + hash: hash, + index: index, + input: input, + nonce: nonce, + r: r, + s: s, + to_address_hash: to_address_hash, + v: v, + value: value, + transaction_index: index, + type: type, + max_priority_fee_per_gas: max_priority_fee_per_gas, + max_fee_per_gas: max_fee_per_gas + } + + if transaction["creates"] do + Map.put(result, :created_contract_address_hash, transaction["creates"]) + else + result + end + end + def elixir_to_params( %{ "blockHash" => block_hash, @@ -250,6 +340,27 @@ defmodule EthereumJSONRPC.Transaction do end end + # Geth pending transactions don't include `r,s,v` transaction fields. + def elixir_to_params( + %{ + "blockHash" => _, + "blockNumber" => _, + "from" => _, + "gas" => _, + "gasPrice" => _, + "hash" => _, + "input" => _, + "nonce" => _, + "to" => _, + "transactionIndex" => _, + "value" => _ + } = transaction + ) do + transaction + |> Map.merge(%{"r" => 0, "s" => 0, "v" => 0}) + |> elixir_to_params() + end + @doc """ Extracts `t:EthereumJSONRPC.hash/0` from transaction `params` diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex index 0374165477ca..2468636538a5 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex @@ -18,11 +18,11 @@ defmodule EthereumJSONRPC.Variant do Fetch the block reward contract beneficiaries for a given blocks from the variant of the Ethereum JSONRPC API. For more information on block reward contracts see: - https://wiki.parity.io/Block-Reward-Contract.html + https://openethereum.github.io/Block-Reward-Contract ## Returns - * `{:ok, %EthereumJSONRPC.FetchedBeneficiaries{params_list: [%{address_hash: address_hash, block_number: block_number}], errors: %{code: code, message: message, data: %{block_number: block_number}}}` - some beneficiaries were successfully fetched and some may have had errors. + * `{:ok, %EthereumJSONRPC.FetchedBeneficiaries{params_set: [%{address_hash: address_hash, block_number: block_number}], errors: %{code: code, message: message, data: %{block_number: block_number}}}` - some beneficiaries were successfully fetched and some may have had errors. * `{:error, reason}` - there was an error at the transport level * `:ignore` - the variant does not support fetching beneficiaries """ @@ -94,4 +94,22 @@ defmodule EthereumJSONRPC.Variant do ], EthereumJSONRPC.json_rpc_named_arguments() ) :: {:ok, [raw_trace_params]} | {:error, reason :: term} | :ignore + + def get do + variant = System.get_env("ETHEREUM_JSONRPC_VARIANT", "nethermind") + + cond do + is_nil(variant) -> + "nethermind" + + variant == "parity" -> + "nethermind" + + true -> + variant + |> String.split(".") + |> List.last() + |> String.downcase() + end + end end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 2c5dc7a15541..e0012546bd8c 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -15,7 +15,7 @@ defmodule EthereumJsonrpc.MixProject do plt_add_apps: [:mix], ignore_warnings: "../../.dialyzer-ignore" ], - elixir: "~> 1.10", + elixir: "~> 1.13", elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", preferred_cli_env: [ @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "0.1.0" + version: "4.1.8" ] end @@ -69,7 +69,7 @@ defmodule EthereumJsonrpc.MixProject do # Log errors and application output to separate files {:logger_file_backend, "~> 0.0.10"}, # Mocking `EthereumJSONRPC.Transport` and `EthereumJSONRPC.HTTP` so we avoid hitting real chains for local testing - {:mox, "~> 0.4", only: [:test]}, + {:mox, "~> 1.0", only: [:test]}, # Tracing {:spandex, "~> 3.0"}, # `:spandex` integration with Datadog diff --git a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js index fd0080f0e97f..9002a7869e9b 100644 --- a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js +++ b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js @@ -442,24 +442,25 @@ }, putGas(call) { - const gasBigInt = call.gasBigInt; - delete call.gasBigInt; - if (gasBigInt === undefined) { - gasBigInt = bigInt.zero; + if (call.gasBigInt === undefined) { + call.gas = '0x0'; + } else { + call.gas = '0x' + call.gasBigInt.toString(16); } - call.gas = '0x' + gasBigInt.toString(16); + delete call.gasBigInt; + }, putGasUsed(call) { - const gasUsedBigInt = call.gasUsedBigInt; - delete call.gasUsedBigInt; - if (gasUsedBigInt === undefined) { - gasUsedBigInt = bigInt.zero; + if (call.gasUsedBigInt === undefined) { + call.gasUsed = '0x0'; + } else { + call.gasUsed = '0x' + call.gasUsedBigInt.toString(16); } - call.gasUsed = '0x' + gasUsedBigInt.toString(16); + delete call.gasUsedBigInt; } } diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/fetched_beneficiaries_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/fetched_beneficiaries_test.exs similarity index 98% rename from apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/fetched_beneficiaries_test.exs rename to apps/ethereum_jsonrpc/test/ethereum_jsonrpc/fetched_beneficiaries_test.exs index 1bc4e90f3376..b2450f2d6d33 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/fetched_beneficiaries_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/fetched_beneficiaries_test.exs @@ -1,8 +1,8 @@ -defmodule EthereumJSONRPC.Parity.FetchedBeneficiariesTest do +defmodule EthereumJSONRPC.FetchedBeneficiariesTest do use ExUnit.Case, async: true alias EthereumJSONRPC - alias EthereumJSONRPC.Parity.FetchedBeneficiaries + alias EthereumJSONRPC.FetchedBeneficiaries describe "from_responses/2" do test "when block is not found" do diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/call_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/call_test.exs index f4a97c07dec4..686ba4f13a1d 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/call_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/call_test.exs @@ -41,7 +41,8 @@ defmodule EthereumJSONRPC.Geth.CallTest do transaction_hash: "0xbc38745b826f058ed2f6c93fa5b145323857f06bbb5230b6a6a50e09e0915857", transaction_index: 0, type: "call", - value: 0 + value: 0, + error: "execution reverted" } end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 9334fd23e2e5..3d812211d3b3 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -5,7 +5,7 @@ defmodule EthereumJSONRPC.GethTest do alias EthereumJSONRPC.Geth - @moduletag :no_parity + @moduletag :no_nethermind describe "fetch_internal_transactions/2" do # Infura Mainnet does not support debug_traceTransaction, so this cannot be tested expect in Mox diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs index 43c0622a40ff..052667657d51 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs @@ -20,7 +20,7 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do http_options: http_options() ], # Which one does not matter, so pick one - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ] } end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace/action_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace/action_test.exs new file mode 100644 index 000000000000..467a9938bbce --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace/action_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.Nethermind.Trace.ActionTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Nethermind.Trace.Action +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace/result_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace/result_test.exs new file mode 100644 index 000000000000..cbd19a3a54c1 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace/result_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.Nethermind.Trace.ResultTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Nethermind.Trace.Result +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace_test.exs new file mode 100644 index 000000000000..5a75a6d2aef2 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind/trace_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.Nethermind.TraceTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Nethermind.Trace +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind_test.exs similarity index 97% rename from apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs rename to apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind_test.exs index 2210d90ee869..0d7eb0ad2988 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/nethermind_test.exs @@ -1,4 +1,4 @@ -defmodule EthereumJSONRPC.ParityTest do +defmodule EthereumJSONRPC.NethermindTest do use ExUnit.Case, async: true use EthereumJSONRPC.Case @@ -9,13 +9,13 @@ defmodule EthereumJSONRPC.ParityTest do setup :verify_on_exit! - doctest EthereumJSONRPC.Parity + doctest EthereumJSONRPC.Nethermind @moduletag :no_geth describe "fetch_internal_transactions/1" do test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do - EthereumJSONRPC.Parity.fetch_internal_transactions([], json_rpc_named_arguments) + EthereumJSONRPC.Nethermind.fetch_internal_transactions([], json_rpc_named_arguments) end end @@ -78,7 +78,7 @@ defmodule EthereumJSONRPC.ParityTest do end) end - assert EthereumJSONRPC.Parity.fetch_block_internal_transactions( + assert EthereumJSONRPC.Nethermind.fetch_block_internal_transactions( [block_number], json_rpc_named_arguments ) == { @@ -164,7 +164,7 @@ defmodule EthereumJSONRPC.ParityTest do end) end - assert EthereumJSONRPC.Parity.fetch_first_trace( + assert EthereumJSONRPC.Nethermind.fetch_first_trace( [ %{ hash_data: transaction_hash, @@ -197,7 +197,7 @@ defmodule EthereumJSONRPC.ParityTest do json_rpc_named_arguments: [ transport: EthereumJSONRPC.Mox, transport_options: [], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ] } ] @@ -259,7 +259,7 @@ defmodule EthereumJSONRPC.ParityTest do end assert {:ok, %FetchedBeneficiaries{params_set: params_set}} = - EthereumJSONRPC.Parity.fetch_beneficiaries([5_080_887], json_rpc_named_arguments) + EthereumJSONRPC.Nethermind.fetch_beneficiaries([5_080_887], json_rpc_named_arguments) assert Enum.count(params_set) == 2 @@ -333,7 +333,7 @@ defmodule EthereumJSONRPC.ParityTest do end assert {:ok, %FetchedBeneficiaries{params_set: params_set, errors: []}} = - EthereumJSONRPC.Parity.fetch_beneficiaries([5_609_295], json_rpc_named_arguments) + EthereumJSONRPC.Nethermind.fetch_beneficiaries([5_609_295], json_rpc_named_arguments) assert Enum.count(params_set) == 2 @@ -364,7 +364,7 @@ defmodule EthereumJSONRPC.ParityTest do end) assert {:ok, %FetchedBeneficiaries{params_set: params_set}} = - EthereumJSONRPC.Parity.fetch_beneficiaries([5_080_887], json_rpc_named_arguments) + EthereumJSONRPC.Nethermind.fetch_beneficiaries([5_080_887], json_rpc_named_arguments) assert Enum.empty?(params_set) end @@ -441,7 +441,7 @@ defmodule EthereumJSONRPC.ParityTest do end assert {:ok, %FetchedBeneficiaries{params_set: params_set}} = - EthereumJSONRPC.Parity.fetch_beneficiaries([5_077_429], json_rpc_named_arguments) + EthereumJSONRPC.Nethermind.fetch_beneficiaries([5_077_429], json_rpc_named_arguments) assert Enum.count(params_set) == 2 @@ -561,7 +561,7 @@ defmodule EthereumJSONRPC.ParityTest do end assert {:ok, %FetchedBeneficiaries{params_set: params_set}} = - EthereumJSONRPC.Parity.fetch_beneficiaries( + EthereumJSONRPC.Nethermind.fetch_beneficiaries( [block_number1, block_number2], json_rpc_named_arguments ) @@ -609,7 +609,7 @@ defmodule EthereumJSONRPC.ParityTest do {:error, "oops"} end) - assert {:error, "oops"} = EthereumJSONRPC.Parity.fetch_beneficiaries([5_080_887], json_rpc_named_arguments) + assert {:error, "oops"} = EthereumJSONRPC.Nethermind.fetch_beneficiaries([5_080_887], json_rpc_named_arguments) end end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs deleted file mode 100644 index 78597b57f1e0..000000000000 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule EthereumJSONRPC.Parity.Trace.ActionTest do - use ExUnit.Case, async: true - - doctest EthereumJSONRPC.Parity.Trace.Action -end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs deleted file mode 100644 index f419f5045b91..000000000000 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule EthereumJSONRPC.Parity.Trace.ResultTest do - use ExUnit.Case, async: true - - doctest EthereumJSONRPC.Parity.Trace.Result -end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs deleted file mode 100644 index 75daf2eb0426..000000000000 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule EthereumJSONRPC.Parity.TraceTest do - use ExUnit.Case, async: true - - doctest EthereumJSONRPC.Parity.Trace -end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs index 468e0f0f30e5..f554c31ce572 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs @@ -46,7 +46,7 @@ defmodule EthereumJSONRPC.ReceiptsTest do transaction_index: 17 } - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> %{ created_contract_address_hash: nil, block_hash: nil, diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs index 75827da43878..88dfbdf5f0fd 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -18,7 +18,7 @@ defmodule EthereumJSONRPCTest do expected_fetched_balance = case Keyword.fetch!(json_rpc_named_arguments, :variant) do EthereumJSONRPC.Geth -> 0 - EthereumJSONRPC.Parity -> 1 + EthereumJSONRPC.Nethermind -> 1 variant -> raise ArgumentError, "Unsupported variant (#{variant}})" end @@ -56,7 +56,7 @@ defmodule EthereumJSONRPCTest do EthereumJSONRPC.Geth -> "invalid argument 0: json: cannot unmarshal hex string of odd length into Go value of type common.Address" - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> "Invalid params: invalid length 1, expected a 0x-prefixed hex string with length of 40." _ -> @@ -174,7 +174,7 @@ defmodule EthereumJSONRPCTest do end describe "fetch_codes/2" do - @tag :no_parity + @tag :no_nethermind test "returns both codes and errors", %{ json_rpc_named_arguments: json_rpc_named_arguments } do @@ -251,7 +251,7 @@ defmodule EthereumJSONRPCTest do test "can fetch blocks", %{json_rpc_named_arguments: json_rpc_named_arguments} do %{block_hash: block_hash, transaction_hash: transaction_hash} = case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> %{ block_hash: "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c", transaction_hash: "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e" @@ -668,7 +668,7 @@ defmodule EthereumJSONRPCTest do test "with valid transaction hash", %{json_rpc_named_arguments: json_rpc_named_arguments} do hash = case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e" EthereumJSONRPC.Geth -> @@ -890,7 +890,7 @@ defmodule EthereumJSONRPCTest do test "fetches net version", %{json_rpc_named_arguments: json_rpc_named_arguments} do expected_version = case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> 77 + EthereumJSONRPC.Nethermind -> 77 _variant -> 1 end @@ -913,3 +913,65 @@ defmodule EthereumJSONRPCTest do end end end + +defmodule EthereumJSONRPCSyncTest do + use EthereumJSONRPC.Case, async: false + + import EthereumJSONRPC.Case + import Mox + + alias EthereumJSONRPC.FetchedBalances + setup :verify_on_exit! + + @moduletag :capture_log + + describe "fetch_balances/1" do + setup do + initial_env = Application.get_all_env(:indexer) + on_exit(fn -> Application.put_all_env([{:indexer, initial_env}]) end) + end + + test "ignores all request with block_quantity != latest when env ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES is true", + %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + expected_fetched_balance = 1 + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_getBalance", + params: [^hash, "latest"] + } + ], + _options -> + {:ok, [%{id: 0, result: EthereumJSONRPC.integer_to_quantity(expected_fetched_balance)}]} + end) + + Application.put_env(:ethereum_jsonrpc, :disable_archive_balances?, "true") + + assert EthereumJSONRPC.fetch_balances( + [ + %{block_quantity: "0x1", hash_data: hash}, + %{block_quantity: "0x2", hash_data: hash}, + %{block_quantity: "0x3", hash_data: hash}, + %{block_quantity: "0x4", hash_data: hash}, + %{block_quantity: "latest", hash_data: hash} + ], + json_rpc_named_arguments + ) == + {:ok, + %FetchedBalances{ + params_list: [ + %{ + address_hash: hash, + block_number: :error, + value: expected_fetched_balance + } + ] + }} + end + end +end diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case.ex index 34c62e5de468..fbbee61574da 100644 --- a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case.ex +++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case.ex @@ -28,7 +28,7 @@ defmodule EthereumJSONRPC.Case do require Logger setup do - module("ETHEREUM_JSONRPC_CASE", "EthereumJSONRPC.Case.Parity.Mox").setup() + module("ETHEREUM_JSONRPC_CASE", "EthereumJSONRPC.Case.Nethermind.Mox").setup() end def log_bad_gateway(under_test, assertions) do diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/parity/http_websocket.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/nethermind/http_websocket.ex similarity index 62% rename from apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/parity/http_websocket.ex rename to apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/nethermind/http_websocket.ex index 8a4fdd8dacbe..0c704d3bf5d3 100644 --- a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/parity/http_websocket.ex +++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/nethermind/http_websocket.ex @@ -1,11 +1,11 @@ -defmodule EthereumJSONRPC.Case.Parity.HTTPWebSocket do +defmodule EthereumJSONRPC.Case.Nethermind.HTTPWebSocket do @moduledoc """ - `EthereumJSONRPC.Case` for connecting to Parity using `EthereumJSONRPC.HTTP` for `json_rpc_named_arguments` + `EthereumJSONRPC.Case` for connecting to Nethermind using `EthereumJSONRPC.HTTP` for `json_rpc_named_arguments` `transport` and `EthereumJSONRPC.WebSocket` for `subscribe_named_arguments` `transport`. """ def setup do - EthereumJSONRPC.WebSocket.Case.Parity.setup() + EthereumJSONRPC.WebSocket.Case.Nethermind.setup() |> Map.put( :json_rpc_named_arguments, transport: EthereumJSONRPC.HTTP, @@ -14,7 +14,7 @@ defmodule EthereumJSONRPC.Case.Parity.HTTPWebSocket do http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]], url: "http://3.85.253.242:8545" ], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ) end end diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/nethermind/mox.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/nethermind/mox.ex new file mode 100644 index 000000000000..18b25d4cca55 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/nethermind/mox.ex @@ -0,0 +1,17 @@ +defmodule EthereumJSONRPC.Case.Nethermind.Mox do + @moduledoc """ + `EthereumJSONRPC.Case` for mocking connecting to Nethermind using `Mox` + """ + + def setup do + %{ + block_interval: 500, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [], + variant: EthereumJSONRPC.Nethermind + ], + subscribe_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: []] + } + end +end diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/parity/mox.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/parity/mox.ex deleted file mode 100644 index 058c8b603451..000000000000 --- a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/parity/mox.ex +++ /dev/null @@ -1,13 +0,0 @@ -defmodule EthereumJSONRPC.Case.Parity.Mox do - @moduledoc """ - `EthereumJSONRPC.Case` for mocking connecting to Parity using `Mox` - """ - - def setup do - %{ - block_interval: 500, - json_rpc_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: [], variant: EthereumJSONRPC.Parity], - subscribe_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: []] - } - end -end diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/web_socket/case/parity.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/web_socket/case/nethermind.ex similarity index 85% rename from apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/web_socket/case/parity.ex rename to apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/web_socket/case/nethermind.ex index d84469aaf959..5d870bfa1e03 100644 --- a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/web_socket/case/parity.ex +++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/web_socket/case/nethermind.ex @@ -1,6 +1,6 @@ -defmodule EthereumJSONRPC.WebSocket.Case.Parity do +defmodule EthereumJSONRPC.WebSocket.Case.Nethermind do @moduledoc """ - `EthereumJSONRPC.WebSocket.Case` connecting to Parity. + `EthereumJSONRPC.WebSocket.Case` connecting to Nethermind. """ import ExUnit.Callbacks, only: [start_supervised!: 1] diff --git a/apps/explorer/README.md b/apps/explorer/README.md index 2230feff3ee8..4064224aba7d 100644 --- a/apps/explorer/README.md +++ b/apps/explorer/README.md @@ -2,35 +2,32 @@ This is a tool for inspecting and analyzing the POA Network blockchain. - ## Machine Requirements * Erlang/OTP 21+ * Elixir 1.9+ * Postgres 10.3 - ## Required Accounts * Github for code storage - ## Setup Instructions ### Development To get BlockScout up and running locally: - * Install dependencies with `$ mix do deps.get, local.rebar, deps.compile, compile` - * Create and migrate your database with `$ mix ecto.create && mix ecto.migrate` - * Run IEx (Interactive Elixir) to access the index and explore: `$ iex -S mix` +* Install dependencies with `$ mix do deps.get, local.rebar, deps.compile, compile` +* Create and migrate your database with `$ mix ecto.create && mix ecto.migrate` +* Run IEx (Interactive Elixir) to access the index and explore: `$ iex -S mix` ### Testing - * Format the Elixir code: `$ mix format` - * Lint the Elixir code: `$ mix credo --strict` - * Run the dialyzer: `mix dialyzer --halt-exit-status` - * Check the Elixir code for vulnerabilities: `$ mix sobelow --config` +* Format the Elixir code: `$ mix format` +* Lint the Elixir code: `$ mix credo --strict` +* Run the dialyzer: `mix dialyzer --halt-exit-status` +* Check the Elixir code for vulnerabilities: `$ mix sobelow --config` ### Benchmarking diff --git a/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs b/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs index 8b6096658ac0..335844236ea0 100644 --- a/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs +++ b/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs @@ -8,7 +8,7 @@ alias Explorer.Chain.Block Benchee.run( %{ "Explorer.Chain.recent_collated_transactions" => fn _ -> - Chain.recent_collated_transactions() + Chain.recent_collated_transactions(true) end }, inputs: %{ diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 0b5a3efffd89..6d5425bda944 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -5,66 +5,26 @@ # is restricted to this project. import Config -disable_indexer = System.get_env("DISABLE_INDEXER") -disable_webapp = System.get_env("DISABLE_WEBAPP") - # General application configuration config :explorer, - ecto_repos: [Explorer.Repo], - coin: System.get_env("COIN") || "POA", - token_functions_reader_max_retries: 3, - allowed_evm_versions: - System.get_env("ALLOWED_EVM_VERSIONS") || - "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,default", - include_uncles_in_average_block_time: - if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "true", do: true, else: false), - healthy_blocks_period: System.get_env("HEALTHY_BLOCKS_PERIOD") || :timer.minutes(5), - realtime_events_sender: - if(disable_webapp != "true", - do: Explorer.Chain.Events.SimpleSender, - else: Explorer.Chain.Events.DBSender - ) + ecto_repos: [Explorer.Repo, Explorer.Repo.Account], + token_functions_reader_max_retries: 3 config :explorer, Explorer.Counters.AverageBlockTime, enabled: true, period: :timer.minutes(10) -config :explorer, Explorer.Chain.Events.Listener, - enabled: - if(disable_webapp == "true" && disable_indexer == "true", - do: false, - else: true - ) +config :explorer, Explorer.ChainSpec.GenesisData, enabled: true -config :explorer, Explorer.ChainSpec.GenesisData, - enabled: true, - chain_spec_path: System.get_env("CHAIN_SPEC_PATH"), - emission_format: System.get_env("EMISSION_FORMAT", "DEFAULT"), - rewards_contract_address: System.get_env("REWARDS_CONTRACT", "0xeca443e8e1ab29971a45a9c57a6a9875701698a5") - -config :explorer, Explorer.Chain.Cache.BlockNumber, - enabled: true, - ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), - global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) - -address_sum_global_ttl = - "CACHE_ADDRESS_SUM_PERIOD" - |> System.get_env("") - |> Integer.parse() - |> case do - {integer, ""} -> :timer.seconds(integer) - _ -> :timer.minutes(60) - end +config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true config :explorer, Explorer.Chain.Cache.AddressSum, enabled: true, - ttl_check_interval: :timer.seconds(1), - global_ttl: address_sum_global_ttl + ttl_check_interval: :timer.seconds(1) config :explorer, Explorer.Chain.Cache.AddressSumMinusBurnt, enabled: true, - ttl_check_interval: :timer.seconds(1), - global_ttl: address_sum_global_ttl + ttl_check_interval: :timer.seconds(1) cache_address_with_balances_update_interval = System.get_env("CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL") @@ -94,6 +54,26 @@ config :explorer, Explorer.Counters.AddressTokenUsdSum, enabled: true, enable_consolidation: true +config :explorer, Explorer.Chain.Cache.ContractsCounter, + enabled: true, + enable_consolidation: true, + update_interval_in_seconds: 30 * 60 + +config :explorer, Explorer.Chain.Cache.NewContractsCounter, + enabled: true, + enable_consolidation: true, + update_interval_in_seconds: 30 * 60 + +config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, + enabled: true, + enable_consolidation: true, + update_interval_in_seconds: 30 * 60 + +config :explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter, + enabled: true, + enable_consolidation: true, + update_interval_in_seconds: 30 * 60 + config :explorer, Explorer.Chain.Cache.TokenExchangeRate, enabled: true, enable_consolidation: true @@ -122,59 +102,17 @@ config :explorer, Explorer.Counters.BlockPriorityFeeCounter, enabled: true, enable_consolidation: true +config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: true + config :explorer, Explorer.Chain.Cache.GasUsage, enabled: System.get_env("CACHE_ENABLE_TOTAL_GAS_USAGE_COUNTER") == "true" -config :explorer, Explorer.ExchangeRates, - enabled: System.get_env("DISABLE_EXCHANGE_RATES") != "true", - store: :ets, - coingecko_coin_id: System.get_env("EXCHANGE_RATES_COINGECKO_COIN_ID"), - coingecko_api_key: System.get_env("EXCHANGE_RATES_COINGECKO_API_KEY"), - coinmarketcap_api_key: System.get_env("EXCHANGE_RATES_COINMARKETCAP_API_KEY"), - fetch_btc_value: System.get_env("EXCHANGE_RATES_FETCH_BTC_VALUE") == "true" - -exchange_rates_source = - cond do - System.get_env("EXCHANGE_RATES_SOURCE") == "coin_gecko" -> Explorer.ExchangeRates.Source.CoinGecko - System.get_env("EXCHANGE_RATES_SOURCE") == "coin_market_cap" -> Explorer.ExchangeRates.Source.CoinMarketCap - true -> Explorer.ExchangeRates.Source.CoinGecko - end - -config :explorer, Explorer.ExchangeRates.Source, source: exchange_rates_source - -config :explorer, Explorer.KnownTokens, enabled: System.get_env("DISABLE_KNOWN_TOKENS") != "true", store: :ets - config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: :timer.seconds(2) -config :explorer, Explorer.Market.History.Cataloger, enabled: disable_indexer != "true" +config :explorer, Explorer.Tags.AddressTag.Cataloger, enabled: true config :explorer, Explorer.Chain.Cache.MinMissingBlockNumber, enabled: System.get_env("DISABLE_WRITE_API") != "true" -txs_stats_init_lag = - System.get_env("TXS_HISTORIAN_INIT_LAG", "0") - |> Integer.parse() - |> elem(0) - |> :timer.minutes() - -txs_stats_days_to_compile_at_init = - System.get_env("TXS_STATS_DAYS_TO_COMPILE_AT_INIT", "40") - |> Integer.parse() - |> elem(0) - -config :explorer, Explorer.Chain.Transaction.History.Historian, - enabled: System.get_env("ENABLE_TXS_STATS", "true") != "false", - init_lag: txs_stats_init_lag, - days_to_compile_at_init: txs_stats_days_to_compile_at_init - -history_fetch_interval = - case Integer.parse(System.get_env("HISTORY_FETCH_INTERVAL", "")) do - {mins, ""} -> mins - _ -> 60 - end - |> :timer.minutes() - -config :explorer, Explorer.History.Process, history_fetch_interval: history_fetch_interval - config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime_usec] config :explorer, Explorer.Tracer, @@ -182,31 +120,8 @@ config :explorer, Explorer.Tracer, adapter: SpandexDatadog.Adapter, trace_key: :blockscout -if System.get_env("METADATA_CONTRACT") && System.get_env("VALIDATORS_CONTRACT") do - config :explorer, Explorer.Validator.MetadataRetriever, - metadata_contract_address: System.get_env("METADATA_CONTRACT"), - validators_contract_address: System.get_env("VALIDATORS_CONTRACT") - - config :explorer, Explorer.Validator.MetadataProcessor, enabled: disable_indexer != "true" -else - config :explorer, Explorer.Validator.MetadataProcessor, enabled: false -end - -config :explorer, Explorer.Chain.Block.Reward, - validators_contract_address: System.get_env("VALIDATORS_CONTRACT"), - keys_manager_contract_address: System.get_env("KEYS_MANAGER_CONTRACT") - -case System.get_env("SUPPLY_MODULE") do - "rsk" -> - config :explorer, supply: Explorer.Chain.Supply.RSK - - _ -> - :ok -end - config :explorer, - solc_bin_api_url: "https://solc-bin.ethereum.org", - checksum_function: System.get_env("CHECKSUM_FUNCTION") && String.to_atom(System.get_env("CHECKSUM_FUNCTION")) + solc_bin_api_url: "https://solc-bin.ethereum.org" config :logger, :explorer, # keep synced with `config/config.exs` @@ -221,28 +136,6 @@ config :spandex_ecto, SpandexEcto.EctoLogger, tracer: Explorer.Tracer, otp_app: :explorer -config :explorer, Explorer.Chain.Cache.Blocks, - ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), - global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) - -config :explorer, Explorer.Chain.Cache.Transactions, - ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), - global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) - -config :explorer, Explorer.Chain.Cache.Accounts, - ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), - global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) - -config :explorer, Explorer.Chain.Cache.Uncles, - ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), - global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) - -config :explorer, Explorer.ThirdPartyIntegrations.Sourcify, - server_url: System.get_env("SOURCIFY_SERVER_URL") || "https://sourcify.dev/server", - enabled: System.get_env("ENABLE_SOURCIFY_INTEGRATION") == "true", - chain_id: System.get_env("CHAIN_ID"), - repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" - # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 552e5d29a19d..821c11c17a6d 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -1,41 +1,15 @@ import Config -database = if System.get_env("DATABASE_URL"), do: nil, else: "explorer_dev" -hostname = if System.get_env("DATABASE_URL"), do: nil, else: "localhost" - -database_api_url = - if System.get_env("DATABASE_READ_ONLY_API_URL"), - do: System.get_env("DATABASE_READ_ONLY_API_URL"), - else: System.get_env("DATABASE_URL") - -pool_size = - if System.get_env("DATABASE_READ_ONLY_API_URL"), - do: String.to_integer(System.get_env("POOL_SIZE", "40")), - else: String.to_integer(System.get_env("POOL_SIZE", "50")) - # Configure your database config :explorer, Explorer.Repo, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - pool_size: pool_size, - timeout: :timer.seconds(80) - -database_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: database -hostname_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: hostname - -pool_size_api = - if System.get_env("DATABASE_READ_ONLY_API_URL"), - do: String.to_integer(System.get_env("POOL_SIZE_API", "50")), - else: String.to_integer(System.get_env("POOL_SIZE_API", "10")) + timeout: :timer.seconds(80), + migration_lock: nil # Configure API database -config :explorer, Explorer.Repo.Replica1, - database: database_api, - hostname: hostname_api, - url: database_api_url, - pool_size: pool_size_api, - timeout: :timer.seconds(80) +config :explorer, Explorer.Repo.Replica1, timeout: :timer.seconds(80) + +# Configure Account database +config :explorer, Explorer.Repo.Account, timeout: :timer.seconds(80) config :explorer, Explorer.Tracer, env: "dev", disabled?: true @@ -52,17 +26,3 @@ config :logger, :token_instances, level: :debug, path: Path.absname("logs/dev/explorer/tokens/token_instances.log"), metadata_filter: [fetcher: :token_instances] - -variant = - if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do - "ganache" - else - System.get_env("ETHEREUM_JSONRPC_VARIANT") - |> String.split(".") - |> List.last() - |> String.downcase() - end - -# Import variant specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "dev/#{variant}.exs" diff --git a/apps/explorer/config/dev/parity.exs b/apps/explorer/config/dev/erigon.exs similarity index 92% rename from apps/explorer/config/dev/parity.exs rename to apps/explorer/config/dev/erigon.exs index 4008d5eb6d03..6deadfa21fe9 100644 --- a/apps/explorer/config/dev/parity.exs +++ b/apps/explorer/config/dev/erigon.exs @@ -13,7 +13,7 @@ config :explorer, ], http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Erigon ], subscribe_named_arguments: [ transport: EthereumJSONRPC.WebSocket, @@ -21,5 +21,5 @@ config :explorer, web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, url: System.get_env("ETHEREUM_JSONRPC_WS_URL") ], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Erigon ] diff --git a/apps/explorer/config/dev/geth.exs b/apps/explorer/config/dev/geth.exs index f5ba58f1e4f8..6443b3c8f60a 100644 --- a/apps/explorer/config/dev/geth.exs +++ b/apps/explorer/config/dev/geth.exs @@ -1,12 +1,21 @@ import Config +hackney_opts_base = [pool: :ethereum_jsonrpc] + +hackney_opts = + if System.get_env("ETHEREUM_JSONRPC_HTTP_INSECURE", "") == "true" do + [:insecure] ++ hackney_opts_base + else + hackney_opts_base + end + config :explorer, json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", - http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: hackney_opts] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/explorer/config/dev/nethermind.exs b/apps/explorer/config/dev/nethermind.exs new file mode 100644 index 000000000000..3a898aba469d --- /dev/null +++ b/apps/explorer/config/dev/nethermind.exs @@ -0,0 +1,25 @@ +import Config + +config :explorer, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", + method_to_url: [ + eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" + ], + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + ], + variant: EthereumJSONRPC.Nethermind + ], + subscribe_named_arguments: [ + transport: EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ], + variant: EthereumJSONRPC.Nethermind + ] diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index cf1a2d010a6c..43be5c0e9146 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -1,33 +1,18 @@ import Config -pool_size = - if System.get_env("DATABASE_READ_ONLY_API_URL"), - do: String.to_integer(System.get_env("POOL_SIZE", "50")), - else: String.to_integer(System.get_env("POOL_SIZE", "40")) - # Configures the database config :explorer, Explorer.Repo, - url: System.get_env("DATABASE_URL"), - pool_size: pool_size, - ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"), prepare: :unnamed, - timeout: :timer.seconds(60) - -database_api_url = - if System.get_env("DATABASE_READ_ONLY_API_URL"), - do: System.get_env("DATABASE_READ_ONLY_API_URL"), - else: System.get_env("DATABASE_URL") - -pool_size_api = - if System.get_env("DATABASE_READ_ONLY_API_URL"), - do: String.to_integer(System.get_env("POOL_SIZE_API", "50")), - else: String.to_integer(System.get_env("POOL_SIZE_API", "10")) + timeout: :timer.seconds(60), + migration_lock: nil # Configures API the database config :explorer, Explorer.Repo.Replica1, - url: database_api_url, - pool_size: pool_size_api, - ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"), + prepare: :unnamed, + timeout: :timer.seconds(60) + +# Configures Account database +config :explorer, Explorer.Repo.Account, prepare: :unnamed, timeout: :timer.seconds(60) @@ -49,17 +34,3 @@ config :logger, :token_instances, path: Path.absname("logs/prod/explorer/tokens/token_instances.log"), metadata_filter: [fetcher: :token_instances], rotate: %{max_bytes: 52_428_800, keep: 19} - -variant = - if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do - "parity" - else - System.get_env("ETHEREUM_JSONRPC_VARIANT") - |> String.split(".") - |> List.last() - |> String.downcase() - end - -# Import variant specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "prod/#{variant}.exs" diff --git a/apps/explorer/config/prod/parity.exs b/apps/explorer/config/prod/erigon.exs similarity index 91% rename from apps/explorer/config/prod/parity.exs rename to apps/explorer/config/prod/erigon.exs index 796a06643388..b5d95a95d0e9 100644 --- a/apps/explorer/config/prod/parity.exs +++ b/apps/explorer/config/prod/erigon.exs @@ -13,7 +13,7 @@ config :explorer, ], http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Erigon ], subscribe_named_arguments: [ transport: EthereumJSONRPC.WebSocket, @@ -21,5 +21,5 @@ config :explorer, web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, url: System.get_env("ETHEREUM_JSONRPC_WS_URL") ], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Erigon ] diff --git a/apps/explorer/config/prod/geth.exs b/apps/explorer/config/prod/geth.exs index 026c06204749..1b940345a715 100644 --- a/apps/explorer/config/prod/geth.exs +++ b/apps/explorer/config/prod/geth.exs @@ -1,12 +1,21 @@ import Config +hackney_opts_base = [pool: :ethereum_jsonrpc] + +hackney_opts = + if System.get_env("ETHEREUM_JSONRPC_HTTP_INSECURE", "") == "true" do + [:insecure] ++ hackney_opts_base + else + hackney_opts_base + end + config :explorer, json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), - http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: hackney_opts] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/explorer/config/prod/nethermind.exs b/apps/explorer/config/prod/nethermind.exs new file mode 100644 index 000000000000..283bf3d908a3 --- /dev/null +++ b/apps/explorer/config/prod/nethermind.exs @@ -0,0 +1,25 @@ +import Config + +config :explorer, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), + method_to_url: [ + eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") + ], + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + ], + variant: EthereumJSONRPC.Nethermind + ], + subscribe_named_arguments: [ + transport: EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ], + variant: EthereumJSONRPC.Nethermind + ] diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs new file mode 100644 index 000000000000..71de3fbe8390 --- /dev/null +++ b/apps/explorer/config/runtime/test.exs @@ -0,0 +1,38 @@ +import Config + +alias EthereumJSONRPC.Variant + +config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets, fetch_btc_value: true + +config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: false + +config :explorer, Explorer.KnownTokens, enabled: false, store: :ets + +config :explorer, Explorer.Counters.AverageBlockTime, enabled: false + +config :explorer, Explorer.Counters.AddressesWithBalanceCounter, enabled: false, enable_consolidation: false + +# This historian is a GenServer whose init uses a Repo in a Task process. +# This causes a ConnectionOwnership error +config :explorer, Explorer.Chain.Transaction.History.Historian, enabled: false +config :explorer, Explorer.Market.History.Historian, enabled: false + +config :explorer, Explorer.Counters.AddressesCounter, enabled: false, enable_consolidation: false +config :explorer, Explorer.Chain.Cache.ContractsCounter, enabled: false, enable_consolidation: false +config :explorer, Explorer.Chain.Cache.NewContractsCounter, enabled: false, enable_consolidation: false +config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, enabled: false, enable_consolidation: false +config :explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter, enabled: false, enable_consolidation: false + +config :explorer, Explorer.Market.History.Cataloger, enabled: false + +config :explorer, Explorer.Tracer, disabled?: false + +config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: false + +config :explorer, + realtime_events_sender: Explorer.Chain.Events.SimpleSender + +variant = Variant.get() + +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../explorer/config/test") +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../indexer/config/test") diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index a329a3cb4bcf..cf55e854ca5b 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -11,7 +11,8 @@ config :explorer, Explorer.Repo, # Default of `5_000` was too low for `BlockFetcher` test ownership_timeout: :timer.minutes(7), timeout: :timer.seconds(60), - queue_target: 1000 + queue_target: 1000, + migration_lock: nil # Configure API database config :explorer, Explorer.Repo.Replica1, @@ -21,28 +22,21 @@ config :explorer, Explorer.Repo.Replica1, # Default of `5_000` was too low for `BlockFetcher` test ownership_timeout: :timer.minutes(1), timeout: :timer.seconds(60), - queue_target: 1000 - -config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets, fetch_btc_value: true - -config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: false - -config :explorer, Explorer.KnownTokens, enabled: false, store: :ets - -config :explorer, Explorer.Counters.AverageBlockTime, enabled: false - -config :explorer, Explorer.Counters.AddressesWithBalanceCounter, enabled: false, enable_consolidation: false - -# This historian is a GenServer whose init uses a Repo in a Task process. -# This causes a ConnectionOwnership error -config :explorer, Explorer.Chain.Transaction.History.Historian, enabled: false -config :explorer, Explorer.Market.History.Historian, enabled: false - -config :explorer, Explorer.Counters.AddressesCounter, enabled: false, enable_consolidation: false - -config :explorer, Explorer.Market.History.Cataloger, enabled: false + queue_target: 1000, + enable_caching_implementation_data_of_proxy: true, + avg_block_time_as_ttl_cached_implementation_data_of_proxy: false, + fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(20), + implementation_data_fetching_timeout: :timer.seconds(20) -config :explorer, Explorer.Tracer, disabled?: false +# Configure API database +config :explorer, Explorer.Repo.Account, + database: "explorer_test_account", + hostname: "localhost", + pool: Ecto.Adapters.SQL.Sandbox, + # Default of `5_000` was too low for `BlockFetcher` test + ownership_timeout: :timer.minutes(1), + timeout: :timer.seconds(60), + queue_target: 1000 config :logger, :explorer, level: :warn, @@ -50,20 +44,3 @@ config :logger, :explorer, config :explorer, Explorer.ExchangeRates.Source.TransactionAndLog, secondary_source: Explorer.ExchangeRates.Source.OneCoinSource - -config :explorer, - realtime_events_sender: Explorer.Chain.Events.SimpleSender - -variant = - if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do - "parity" - else - System.get_env("ETHEREUM_JSONRPC_VARIANT") - |> String.split(".") - |> List.last() - |> String.downcase() - end - -# Import variant specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "test/#{variant}.exs" diff --git a/apps/explorer/config/test/erigon.exs b/apps/explorer/config/test/erigon.exs new file mode 100644 index 000000000000..268b90cf609c --- /dev/null +++ b/apps/explorer/config/test/erigon.exs @@ -0,0 +1,14 @@ +use Mix.Config + +config :explorer, + transport: EthereumJSONRPC.HTTP, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [], + variant: EthereumJSONRPC.Erigon + ], + subscribe_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [], + variant: EthereumJSONRPC.Erigon + ] diff --git a/apps/explorer/config/test/parity.exs b/apps/explorer/config/test/nethermind.exs similarity index 76% rename from apps/explorer/config/test/parity.exs rename to apps/explorer/config/test/nethermind.exs index c0e9464f7e04..e43ed5f3b415 100644 --- a/apps/explorer/config/test/parity.exs +++ b/apps/explorer/config/test/nethermind.exs @@ -5,10 +5,10 @@ config :explorer, json_rpc_named_arguments: [ transport: EthereumJSONRPC.Mox, transport_options: [], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ], subscribe_named_arguments: [ transport: EthereumJSONRPC.Mox, transport_options: [], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ] diff --git a/apps/explorer/lib/encrypt.ex b/apps/explorer/lib/encrypt.ex new file mode 100644 index 000000000000..e105feb11d7b --- /dev/null +++ b/apps/explorer/lib/encrypt.ex @@ -0,0 +1,116 @@ +defmodule Mix.Tasks.Encrypt do + @moduledoc "The encrypt mix task: `mix help encrypt`" + use Mix.Task + + alias Ecto.Changeset + + alias Explorer.Account.{ + CustomABI, + Identity, + PublicTagsRequest, + TagAddress, + TagTransaction, + WatchlistAddress, + WatchlistNotification + } + + alias Explorer.Repo.Account + alias Mix.Task + + @shortdoc "Encrypt" + def run(_) do + Task.run("app.start") + + Identity + |> Account.all() + |> Enum.each(fn element -> + element + |> Changeset.change(%{ + encrypted_uid: element.uid, + encrypted_email: element.email, + encrypted_name: element.name, + encrypted_nickname: element.nickname, + encrypted_avatar: element.avatar, + uid_hash: element.uid + }) + |> Account.update!() + end) + + TagAddress + |> Account.all() + |> Enum.each(fn element -> + element + |> Changeset.change(%{ + encrypted_name: element.name, + encrypted_address_hash: element.address_hash, + address_hash_hash: element.address_hash |> to_string() |> String.downcase() + }) + |> Account.update!() + end) + + TagTransaction + |> Account.all() + |> Enum.each(fn element -> + element + |> Changeset.change(%{ + encrypted_name: element.name, + encrypted_tx_hash: element.tx_hash, + tx_hash_hash: element.tx_hash |> to_string() |> String.downcase() + }) + |> Account.update!() + end) + + CustomABI + |> Account.all() + |> Enum.each(fn element -> + element + |> Changeset.change(%{ + encrypted_name: element.name, + encrypted_address_hash: element.address_hash, + address_hash_hash: element.address_hash |> to_string() |> String.downcase() + }) + |> Account.update!() + end) + + WatchlistAddress + |> Account.all() + |> Enum.each(fn element -> + element + |> Changeset.change(%{ + encrypted_name: element.name, + encrypted_address_hash: element.address_hash, + address_hash_hash: element.address_hash |> to_string() |> String.downcase() + }) + |> Account.update!() + end) + + WatchlistNotification + |> Account.all() + |> Enum.each(fn element -> + element + |> Changeset.change(%{ + encrypted_name: element.name, + encrypted_from_address_hash: element.from_address_hash, + encrypted_to_address_hash: element.to_address_hash, + encrypted_transaction_hash: element.transaction_hash, + encrypted_subject: element.subject, + from_address_hash_hash: element.from_address_hash |> to_string() |> String.downcase(), + to_address_hash_hash: element.to_address_hash |> to_string() |> String.downcase(), + transaction_hash_hash: element.transaction_hash |> to_string() |> String.downcase(), + subject_hash: element.subject + }) + |> Account.update!() + end) + + PublicTagsRequest + |> Account.all() + |> Enum.each(fn element -> + element + |> Changeset.change(%{ + encrypted_full_name: element.full_name, + encrypted_email: element.email + }) + |> Account.update!() + end) + end +end diff --git a/apps/explorer/lib/explorer.ex b/apps/explorer/lib/explorer.ex index 5c52551e40f7..0becee7b1aa3 100644 --- a/apps/explorer/lib/explorer.ex +++ b/apps/explorer/lib/explorer.ex @@ -7,10 +7,16 @@ defmodule Explorer do if it comes from the database, an external API or others. """ + require Logger + @doc """ Returns the configured coin for `Explorer` """ def coin do Application.get_env(:explorer, :coin) end + + def coin_name do + Application.get_env(:explorer, :coin_name) + end end diff --git a/apps/explorer/lib/explorer/access_helpers.ex b/apps/explorer/lib/explorer/access_helpers.ex new file mode 100644 index 000000000000..4bb5e3575d2a --- /dev/null +++ b/apps/explorer/lib/explorer/access_helpers.ex @@ -0,0 +1,31 @@ +defmodule Explorer.AccessHelpers do + @moduledoc """ + Helpers to restrict access to some pages filtering by address + """ + + def restricted_access?(address_hash, params) do + restricted_list_var = Application.get_env(:explorer, :restricted_list) + restricted_list = (restricted_list_var && String.split(restricted_list_var, ",")) || [] + + if Enum.count(restricted_list) > 0 do + formatted_restricted_list = + restricted_list + |> Enum.map(fn addr -> + String.downcase(addr) + end) + + formatted_address_hash = String.downcase(address_hash) + + address_restricted = + formatted_restricted_list + |> Enum.member?(formatted_address_hash) + + key = if params && Map.has_key?(params, "key"), do: Map.get(params, "key"), else: nil + correct_key = key && key == Application.get_env(:explorer, :restricted_list_key) + + if address_restricted && !correct_key, do: {:restricted_access, true}, else: {:ok, false} + else + {:ok, false} + end + end +end diff --git a/apps/explorer/lib/explorer/account.ex b/apps/explorer/lib/explorer/account.ex new file mode 100644 index 000000000000..5f2155b1f4f5 --- /dev/null +++ b/apps/explorer/lib/explorer/account.ex @@ -0,0 +1,9 @@ +defmodule Explorer.Account do + @moduledoc """ + Context for Account module. + """ + + def enabled? do + Application.get_env(:explorer, __MODULE__)[:enabled] + end +end diff --git a/apps/explorer/lib/explorer/account/api/key.ex b/apps/explorer/lib/explorer/account/api/key.ex new file mode 100644 index 000000000000..ab80d41ec234 --- /dev/null +++ b/apps/explorer/lib/explorer/account/api/key.ex @@ -0,0 +1,129 @@ +defmodule Explorer.Account.Api.Key do + @moduledoc """ + Module is responsible for schema for API keys, keys is used to track number of requests to the API endpoints + """ + use Explorer.Schema + + alias Explorer.Account.Identity + alias Ecto.{Changeset, UUID} + alias Explorer.Repo + + import Ecto.Changeset + + @max_key_per_account 3 + + @primary_key false + schema "account_api_keys" do + field(:name, :string) + field(:value, UUID, primary_key: true) + belongs_to(:identity, Identity) + + timestamps() + end + + @attrs ~w(value name identity_id)a + + def changeset do + %__MODULE__{} + |> cast(%{}, @attrs) + end + + def changeset(%__MODULE__{} = api_key, attrs \\ %{}) do + api_key + |> cast(attrs, @attrs) + |> validate_required(@attrs, message: "Required") + |> validate_length(:name, min: 1, max: 255) + |> unique_constraint(:value, message: "API key already exists") + |> foreign_key_constraint(:identity_id, message: "User not found") + |> api_key_count_constraint() + end + + def create(attrs) do + %__MODULE__{} + |> changeset(Map.put(attrs, :value, generate_api_key())) + |> Repo.account_repo().insert() + end + + def api_key_count_constraint(%Changeset{changes: %{identity_id: identity_id}} = api_key) do + if identity_id + |> api_keys_by_identity_id_query() + |> limit(@max_key_per_account) + |> Repo.account_repo().aggregate(:count, :value) >= @max_key_per_account do + api_key + |> add_error(:name, "Max #{@max_key_per_account} keys per account") + else + api_key + end + end + + def api_key_count_constraint(changeset), do: changeset + + def generate_api_key do + UUID.generate() + end + + def api_keys_by_identity_id_query(id) when not is_nil(id) do + __MODULE__ + |> where([api_key], api_key.identity_id == ^id) + |> order_by([api_key], desc: api_key.inserted_at) + end + + def api_keys_by_identity_id_query(_), do: nil + + def api_key_by_value_and_identity_id_query(api_key_value, identity_id) + when not is_nil(api_key_value) and not is_nil(identity_id) do + __MODULE__ + |> where([api_key], api_key.identity_id == ^identity_id and api_key.value == ^api_key_value) + end + + def api_key_by_value_and_identity_id_query(_, _), do: nil + + def get_api_key_by_value_and_identity_id(value, identity_id) when not is_nil(value) and not is_nil(identity_id) do + value + |> api_key_by_value_and_identity_id_query(identity_id) + |> Repo.account_repo().one() + end + + def get_api_key_by_value_and_identity_id(_, _), do: nil + + def update(%{value: api_key_value, identity_id: identity_id} = attrs) do + with api_key <- get_api_key_by_value_and_identity_id(api_key_value, identity_id), + false <- is_nil(api_key) do + api_key |> changeset(attrs) |> Repo.account_repo().update() + else + true -> + {:error, %{reason: :item_not_found}} + end + end + + def delete(api_key_value, identity_id) when not is_nil(api_key_value) and not is_nil(identity_id) do + api_key_value + |> api_key_by_value_and_identity_id_query(identity_id) + |> Repo.account_repo().delete_all() + end + + def delete(_, _), do: nil + + def get_api_keys_by_identity_id(id) when not is_nil(id) do + id + |> api_keys_by_identity_id_query() + |> Repo.account_repo().all() + end + + def get_api_keys_by_identity_id(_), do: nil + + def api_key_with_plan_by_value(api_key_value) when not is_nil(api_key_value) do + if match?({:ok, _casted_api_key}, UUID.cast(api_key_value)) do + __MODULE__ + |> where([api_key], api_key.value == ^api_key_value) + |> Repo.account_repo().one() + |> Repo.account_repo().preload(identity: :plan) + else + nil + end + end + + def api_key_with_plan_by_value(_), do: nil + + def get_max_api_keys_count, do: @max_key_per_account +end diff --git a/apps/explorer/lib/explorer/account/api/plan.ex b/apps/explorer/lib/explorer/account/api/plan.ex new file mode 100644 index 000000000000..b89b33bc9cb7 --- /dev/null +++ b/apps/explorer/lib/explorer/account/api/plan.ex @@ -0,0 +1,13 @@ +defmodule Explorer.Account.Api.Plan do + @moduledoc """ + Module is responsible for schema for API plans, each plan contains its name and maximum number of requests per second + """ + use Explorer.Schema + + schema "account_api_plans" do + field(:name, :string) + field(:max_req_per_second, :integer) + + timestamps() + end +end diff --git a/apps/explorer/lib/explorer/account/custom_abi.ex b/apps/explorer/lib/explorer/account/custom_abi.ex new file mode 100644 index 000000000000..b48596b7ceec --- /dev/null +++ b/apps/explorer/lib/explorer/account/custom_abi.ex @@ -0,0 +1,226 @@ +defmodule Explorer.Account.CustomABI do + @moduledoc """ + Module is responsible for schema for API keys, keys is used to track number of requests to the API endpoints + """ + use Explorer.Schema + + alias ABI.FunctionSelector + alias Ecto.Changeset + alias Explorer.Account.Identity + alias Explorer.{Chain, Repo} + + import Explorer.Chain, only: [hash_to_lower_case_string: 1] + import Ecto.Changeset + + @max_abis_per_account 15 + + schema "account_custom_abis" do + field(:abi, {:array, :map}) + field(:given_abi, :string, virtual: true) + field(:abi_validating_error, :string, virtual: true) + field(:address_hash_hash, Cloak.Ecto.SHA256) + field(:address_hash, Explorer.Encrypted.AddressHash) + field(:name, Explorer.Encrypted.Binary) + + belongs_to(:identity, Identity) + + timestamps() + end + + @attrs ~w(name abi identity_id address_hash)a + + def changeset(%__MODULE__{} = custom_abi \\ %__MODULE__{}, attrs \\ %{}) do + custom_abi + |> cast(check_is_abi_valid?(attrs), @attrs ++ [:id, :given_abi, :abi_validating_error]) + |> validate_required(@attrs, message: "Required") + |> validate_custom_abi() + |> check_smart_contract_address() + |> foreign_key_constraint(:identity_id, message: "User not found") + |> put_hashed_fields() + |> unique_constraint([:identity_id, :address_hash_hash], + message: "Custom ABI for this address has already been added before" + ) + |> custom_abi_count_constraint() + end + + def changeset_without_constraints(%__MODULE__{} = custom_abi \\ %__MODULE__{}, attrs \\ %{}) do + custom_abi + |> cast(attrs, [:id | @attrs]) + |> validate_required(@attrs, message: "Required") + end + + defp put_hashed_fields(changeset) do + changeset + |> put_change(:address_hash_hash, hash_to_lower_case_string(get_field(changeset, :address_hash))) + end + + defp check_smart_contract_address(%Changeset{changes: %{address_hash: address_hash}} = custom_abi) do + check_smart_contract_address_inner(custom_abi, address_hash) + end + + defp check_smart_contract_address(%Changeset{data: %{address_hash: address_hash}} = custom_abi) do + check_smart_contract_address_inner(custom_abi, address_hash) + end + + defp check_smart_contract_address(custom_abi), do: custom_abi + + defp check_smart_contract_address_inner(changeset, address_hash) do + if Chain.is_address_hash_is_smart_contract?(address_hash) do + changeset + else + add_error(changeset, :address_hash, "Address is not a smart contract") + end + end + + defp validate_custom_abi(%Changeset{changes: %{given_abi: given_abi, abi_validating_error: error}} = custom_abi) do + custom_abi + |> add_error(:abi, error) + |> force_change(:abi, given_abi) + end + + defp validate_custom_abi(custom_abi), do: custom_abi + + defp check_is_abi_valid?(%{abi: abi} = custom_abi) when is_binary(abi) do + with {:ok, decoded} <- Jason.decode(abi), + true <- is_list(decoded) do + custom_abi + |> Map.put(:abi, decoded) + |> check_is_abi_valid?(abi) + else + _ -> + custom_abi + |> Map.put(:abi, "") + |> Map.put(:given_abi, abi) + |> Map.put(:abi_validating_error, "Invalid format") + end + end + + defp check_is_abi_valid?(custom_abi, given_abi \\ nil) + + defp check_is_abi_valid?(%{abi: abi} = custom_abi, given_abi) when is_list(abi) do + with true <- length(abi) > 0, + filtered_abi <- filter_abi(abi), + true <- Enum.count(filtered_abi) > 0 do + Map.put(custom_abi, :abi, filtered_abi) + else + _ -> + custom_abi + |> Map.put(:abi, "") + |> (&if(is_nil(given_abi), + do: Map.put(&1, :given_abi, Jason.encode!(abi)), + else: Map.put(&1, :given_abi, given_abi) + )).() + |> Map.put(:abi_validating_error, "ABI must contain functions") + end + end + + defp check_is_abi_valid?(custom_abi, _), do: custom_abi + + defp filter_abi(abi_list) when is_list(abi_list) do + Enum.filter(abi_list, &is_abi_function(&1)) + end + + defp is_abi_function(abi_item) when is_map(abi_item) do + case ABI.parse_specification([abi_item], include_events?: false) do + [%FunctionSelector{type: :constructor}] -> + false + + [_] -> + true + + _ -> + false + end + end + + def custom_abi_count_constraint(%Changeset{changes: %{identity_id: identity_id}} = custom_abi) do + if identity_id + |> custom_abis_by_identity_id_query() + |> limit(@max_abis_per_account) + |> Repo.account_repo().aggregate(:count, :id) >= @max_abis_per_account do + add_error(custom_abi, :name, "Max #{@max_abis_per_account} ABIs per account") + else + custom_abi + end + end + + def custom_abi_count_constraint(%Changeset{} = custom_abi), do: custom_abi + + def create(attrs) do + %__MODULE__{} + |> changeset(attrs) + |> Repo.account_repo().insert() + end + + def custom_abis_by_identity_id_query(id) when not is_nil(id) do + __MODULE__ + |> where([abi], abi.identity_id == ^id) + |> order_by([abi], desc: abi.id) + end + + def custom_abis_by_identity_id_query(_), do: nil + + def custom_abi_by_id_and_identity_id_query(id, identity_id) + when not is_nil(id) and not is_nil(identity_id) do + __MODULE__ + |> where([custom_abi], custom_abi.identity_id == ^identity_id and custom_abi.id == ^id) + end + + def custom_abi_by_id_and_identity_id_query(_, _), do: nil + + def custom_abi_by_identity_id_and_address_hash_query(address_hash, identity_id) + when not is_nil(identity_id) and not is_nil(address_hash) do + __MODULE__ + |> where([custom_abi], custom_abi.identity_id == ^identity_id and custom_abi.address_hash_hash == ^address_hash) + end + + def custom_abi_by_identity_id_and_address_hash_query(_, _), do: nil + + def get_custom_abi_by_identity_id_and_address_hash(address_hash, identity_id) + when not is_nil(identity_id) and not is_nil(address_hash) do + address_hash + |> hash_to_lower_case_string() + |> custom_abi_by_identity_id_and_address_hash_query(identity_id) + |> Repo.account_repo().one() + end + + def get_custom_abi_by_identity_id_and_address_hash(_, _), do: nil + + def get_custom_abi_by_id_and_identity_id(id, identity_id) when not is_nil(id) and not is_nil(identity_id) do + id + |> custom_abi_by_id_and_identity_id_query(identity_id) + |> Repo.account_repo().one() + end + + def get_custom_abi_by_id_and_identity_id(_, _), do: nil + + def get_custom_abis_by_identity_id(id) when not is_nil(id) do + id + |> custom_abis_by_identity_id_query() + |> Repo.account_repo().all() + end + + def get_custom_abis_by_identity_id(_), do: nil + + def delete(id, identity_id) when not is_nil(id) and not is_nil(identity_id) do + id + |> custom_abi_by_id_and_identity_id_query(identity_id) + |> Repo.account_repo().delete_all() + end + + def delete(_, _), do: nil + + def update(%{id: id, identity_id: identity_id} = attrs) do + with custom_abi <- get_custom_abi_by_id_and_identity_id(id, identity_id), + false <- is_nil(custom_abi) do + custom_abi + |> changeset(attrs) + |> Repo.account_repo().update() + else + true -> + {:error, %{reason: :item_not_found}} + end + end + + def get_max_custom_abis_count, do: @max_abis_per_account +end diff --git a/apps/explorer/lib/explorer/account/identity.ex b/apps/explorer/lib/explorer/account/identity.ex new file mode 100644 index 000000000000..d0766d7d3586 --- /dev/null +++ b/apps/explorer/lib/explorer/account/identity.ex @@ -0,0 +1,41 @@ +defmodule Explorer.Account.Identity do + @moduledoc """ + Identity of user fetched via Oauth + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Explorer.Account.Api.Plan + alias Explorer.Account.{TagAddress, Watchlist} + + schema "account_identities" do + field(:uid_hash, Cloak.Ecto.SHA256) + field(:uid, Explorer.Encrypted.Binary) + field(:email, Explorer.Encrypted.Binary) + field(:name, Explorer.Encrypted.Binary) + field(:nickname, Explorer.Encrypted.Binary) + field(:avatar, Explorer.Encrypted.Binary) + + has_many(:tag_addresses, TagAddress) + has_many(:watchlists, Watchlist) + + belongs_to(:plan, Plan) + + timestamps() + end + + @doc false + def changeset(identity, attrs) do + identity + |> cast(attrs, [:uid, :email, :name, :nickname, :avatar]) + |> validate_required([:uid, :email, :name]) + |> put_hashed_fields() + end + + defp put_hashed_fields(changeset) do + changeset + |> put_change(:uid_hash, get_field(changeset, :uid)) + end +end diff --git a/apps/explorer/lib/explorer/account/notifier/email.ex b/apps/explorer/lib/explorer/account/notifier/email.ex new file mode 100644 index 000000000000..35555e316914 --- /dev/null +++ b/apps/explorer/lib/explorer/account/notifier/email.ex @@ -0,0 +1,155 @@ +defmodule Explorer.Account.Notifier.Email do + @moduledoc """ + Composing an email to sendgrid + """ + + require Logger + + alias BlockScoutWeb.WebRouter.Helpers + alias Explorer.Account.{Identity, Watchlist, WatchlistAddress, WatchlistNotification} + alias Explorer.Repo + + import Bamboo.{Email, SendGridHelper} + + def compose(notification, %{notify_email: notify}) when notify do + notification = preload(notification) + + email = compose_email(notification) + Logger.debug("--- composed email", fetcher: :account) + Logger.debug(email, fetcher: :account) + email + end + + def compose(_, _), do: nil + + defp compose_email(notification) do + email = new_email(from: sender(), to: email(notification)) + + email + |> with_template(template()) + |> add_dynamic_field("username", username(notification)) + |> add_dynamic_field("address_hash", address_hash_string(notification)) + |> add_dynamic_field("address_name", notification.watchlist_address.name) + |> add_dynamic_field("transaction_hash", hash_string(notification.transaction_hash)) + |> add_dynamic_field("from_address_hash", hash_string(notification.from_address_hash)) + |> add_dynamic_field("to_address_hash", hash_string(notification.to_address_hash)) + |> add_dynamic_field("block_number", notification.block_number) + |> add_dynamic_field("amount", amount(notification)) + |> add_dynamic_field("name", notification.name) + |> add_dynamic_field("tx_fee", notification.tx_fee) + |> add_dynamic_field("direction", direction(notification)) + |> add_dynamic_field("method", notification.method) + |> add_dynamic_field("transaction_url", transaction_url(notification)) + |> add_dynamic_field("address_url", address_url(notification.watchlist_address.address_hash)) + |> add_dynamic_field("from_url", address_url(notification.from_address_hash)) + |> add_dynamic_field("to_url", address_url(notification.to_address_hash)) + |> add_dynamic_field("block_url", block_url(notification)) + end + + defp amount(%WatchlistNotification{amount: amount, subject: subject, type: type}) do + case type do + "COIN" -> + amount + + "ERC-20" -> + amount + + "ERC-721" -> + "Token ID: " <> subject <> " of " + + "ERC-1155" -> + "Token ID: " <> subject <> " of " + end + end + + defp email(%WatchlistNotification{ + watchlist_address: %WatchlistAddress{ + watchlist: %Watchlist{ + identity: %Identity{ + email: email + } + } + } + }), + do: email + + defp username(%WatchlistNotification{ + watchlist_address: %WatchlistAddress{ + watchlist: %Watchlist{ + identity: %Identity{ + name: name + } + } + } + }), + do: name + + defp address_hash_string(%WatchlistNotification{ + watchlist_address: %WatchlistAddress{address_hash: address_hash} + }), + do: hash_string(address_hash) + + defp hash_string(hash) do + "0x" <> Base.encode16(hash.bytes, case: :lower) + end + + defp direction(notification) do + affect(notification) <> " " <> place(notification) + end + + defp place(%WatchlistNotification{direction: direction}) do + case direction do + "incoming" -> "at" + "outgoing" -> "from" + _ -> "unknown" + end + end + + defp affect(%WatchlistNotification{direction: direction}) do + case direction do + "incoming" -> "received" + "outgoing" -> "sent" + _ -> "unknown" + end + end + + defp preload(notification) do + Repo.account_repo().preload(notification, watchlist_address: [watchlist: :identity]) + end + + defp address_url(address_hash) do + Helpers.address_url(uri(), :show, address_hash) + end + + defp block_url(notification) do + URI.to_string(uri()) <> "block/" <> Integer.to_string(notification.block_number) + end + + defp transaction_url(notification) do + Helpers.transaction_url(uri(), :show, notification.transaction_hash) + end + + defp uri do + %URI{scheme: "https", host: host(), path: path()} + end + + defp host do + if System.get_env("MIX_ENV") == "prod" do + "blockscout.com" + else + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + end + end + + defp path do + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] + end + + defp sender do + Application.get_env(:explorer, Explorer.Account)[:sendgrid][:sender] + end + + defp template do + Application.get_env(:explorer, Explorer.Account)[:sendgrid][:template] + end +end diff --git a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex new file mode 100644 index 000000000000..0186875147ab --- /dev/null +++ b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex @@ -0,0 +1,70 @@ +defmodule Explorer.Account.Notifier.ForbiddenAddress do + @moduledoc """ + Check if address is forbidden to notify + """ + + @blacklist [ + "0x0000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000dEaD" + ] + + alias Explorer.{AccessHelpers, Repo} + alias Explorer.Chain.Token + + import Ecto.Query, only: [from: 2] + import Explorer.Chain, only: [string_to_address_hash: 1] + + def check(address_string) when is_bitstring(address_string) do + case format_address(address_string) do + {:error, message} -> + {:error, message} + + address_hash -> + check(address_hash) + end + end + + def check(%Explorer.Chain.Hash{} = address_hash) do + cond do + address_hash in blacklist() -> + {:error, "This address is blacklisted"} + + is_contract(address_hash) -> + {:error, "This address isn't personal"} + + match?({:restricted_access, true}, AccessHelpers.restricted_access?(to_string(address_hash), %{})) -> + {:error, "This address has restricted access"} + + address_hash -> + {:ok, address_hash} + end + end + + defp is_contract(%Explorer.Chain.Hash{} = address_hash) do + query = + from( + token in Token, + where: token.contract_address_hash == ^address_hash + ) + + contract_addresses = Repo.all(query) + List.first(contract_addresses) + end + + defp format_address(address_hash_string) do + case string_to_address_hash(address_hash_string) do + {:ok, address_hash} -> + address_hash + + :error -> + {:error, "Address is invalid"} + end + end + + defp blacklist do + Enum.map( + @blacklist, + &format_address(&1) + ) + end +end diff --git a/apps/explorer/lib/explorer/account/notifier/notify.ex b/apps/explorer/lib/explorer/account/notifier/notify.ex new file mode 100644 index 000000000000..b64b4bd47a5a --- /dev/null +++ b/apps/explorer/lib/explorer/account/notifier/notify.ex @@ -0,0 +1,153 @@ +defmodule Explorer.Account.Notifier.Notify do + @moduledoc """ + Composing notification, store and send it to email + """ + + alias Explorer.Account.Notifier.{Email, ForbiddenAddress, Summary} + alias Explorer.Account.{WatchlistAddress, WatchlistNotification} + alias Explorer.Chain.{TokenTransfer, Transaction} + alias Explorer.{Mailer, Repo} + + require Logger + + import Ecto.Query, only: [from: 2] + import Explorer.Chain, only: [hash_to_lower_case_string: 1] + + def call(nil), do: nil + def call([]), do: nil + + def call(transactions) when is_list(transactions) do + Enum.map(transactions, fn transaction -> process(transaction) end) + end + + defp process(%TokenTransfer{} = transfer) do + Logger.debug(transfer, fetcher: :account) + + transfer + |> Summary.process() + |> Enum.map(fn summary -> notify_watchlists(summary) end) + end + + defp process(%Transaction{} = transaction) do + Logger.debug(transaction, fetcher: :account) + + transaction + |> Summary.process() + |> Enum.map(fn summary -> notify_watchlists(summary) end) + end + + defp process(_), do: nil + + defp notify_watchlists(%Summary{from_address_hash: nil}), do: nil + defp notify_watchlists(%Summary{to_address_hash: nil}), do: nil + + defp notify_watchlists(%Summary{} = summary) do + incoming_addresses = find_watchlists_addresses(summary.to_address_hash) + outgoing_addresses = find_watchlists_addresses(summary.from_address_hash) + + Logger.debug("--- filled summary", fetcher: :account) + Logger.debug(summary, fetcher: :account) + + Enum.each(incoming_addresses, fn address -> notify_watchlist(address, summary, :incoming) end) + Enum.each(outgoing_addresses, fn address -> notify_watchlist(address, summary, :outgoing) end) + end + + defp notify_watchlists(nil), do: nil + + defp notify_watchlist(%WatchlistAddress{} = address, summary, direction) do + case ForbiddenAddress.check(address.address_hash) do + {:ok, _address_hash} -> + with %WatchlistNotification{} = notification <- + build_watchlist_notification( + address, + summary, + direction + ) do + notification + |> query_notification(address) + |> Repo.account_repo().all() + |> case do + [] -> save_and_send_notification(notification, address) + _ -> :ok + end + end + + {:error, _message} -> + nil + end + end + + defp query_notification(notification, watchlist_address) do + from(wn in WatchlistNotification, + where: + wn.watchlist_address_id == ^watchlist_address.id and + wn.from_address_hash_hash == ^notification.from_address_hash and + wn.to_address_hash_hash == ^notification.to_address_hash and + wn.transaction_hash_hash == ^notification.transaction_hash and + wn.block_number == ^notification.block_number and + wn.direction == ^notification.direction and + wn.subject_hash == ^notification.subject and + wn.amount == ^notification.amount + ) + end + + defp save_and_send_notification(%WatchlistNotification{} = notification, %WatchlistAddress{} = address) do + Repo.account_repo().insert(notification) + + email = Email.compose(notification, address) + + case Mailer.deliver_now(email, response: true) do + {:ok, _email, response} -> + Logger.info("--- email delivery response: SUCCESS", fetcher: :account) + Logger.info(response, fetcher: :account) + + {:error, error} -> + Logger.info("--- email delivery response: FAILED", fetcher: :account) + Logger.info(error, fetcher: :account) + end + end + + @doc """ + direction = :incoming || :outgoing + """ + def build_watchlist_notification(%Explorer.Account.WatchlistAddress{} = address, summary, direction) do + if is_watched(address, summary, direction) do + %WatchlistNotification{ + watchlist_address_id: address.id, + transaction_hash: summary.transaction_hash, + from_address_hash: summary.from_address_hash, + to_address_hash: summary.to_address_hash, + direction: to_string(direction), + method: summary.method, + block_number: summary.block_number, + amount: summary.amount, + subject: summary.subject, + tx_fee: summary.tx_fee, + name: summary.name, + type: summary.type, + from_address_hash_hash: hash_to_lower_case_string(summary.from_address_hash), + to_address_hash_hash: hash_to_lower_case_string(summary.to_address_hash), + transaction_hash_hash: hash_to_lower_case_string(summary.transaction_hash), + subject_hash: summary.subject + } + end + end + + defp is_watched(%WatchlistAddress{} = address, %{type: type}, direction) do + case {type, direction} do + {"COIN", :incoming} -> address.watch_coin_input + {"COIN", :outgoing} -> address.watch_coin_output + {"ERC-20", :incoming} -> address.watch_erc_20_input + {"ERC-20", :outgoing} -> address.watch_erc_20_output + {"ERC-721", :incoming} -> address.watch_erc_721_input + {"ERC-721", :outgoing} -> address.watch_erc_721_output + {"ERC-1155", :incoming} -> address.watch_erc_1155_input + {"ERC-1155", :outgoing} -> address.watch_erc_1155_output + end + end + + defp find_watchlists_addresses(%Explorer.Chain.Hash{} = address_hash) do + query = from(wa in WatchlistAddress, where: wa.address_hash_hash == ^address_hash) + Repo.account_repo().all(query) + end +end diff --git a/apps/explorer/lib/explorer/account/notifier/summary.ex b/apps/explorer/lib/explorer/account/notifier/summary.ex new file mode 100644 index 000000000000..2c795ce252d3 --- /dev/null +++ b/apps/explorer/lib/explorer/account/notifier/summary.ex @@ -0,0 +1,221 @@ +defmodule Explorer.Account.Notifier.Summary do + @moduledoc """ + Compose a summary from transactions + """ + + require Logger + + alias Explorer + alias Explorer.Account.Notifier.Summary + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Wei + + defstruct [ + :transaction_hash, + :from_address_hash, + :to_address_hash, + :method, + :block_number, + :amount, + :tx_fee, + :name, + :subject, + :type + ] + + def process(%Chain.Transaction{} = transaction) do + preloaded_transaction = preload(transaction) + + transfers_summaries = + handle_collection( + transaction, + preloaded_transaction.token_transfers + ) + + transaction_summary = fetch_summary(transaction) + + [transaction_summary | transfers_summaries] + |> Enum.filter(fn summary -> + not (is_nil(summary) or + summary == :nothing or + is_nil(summary.amount) or + summary.amount == Decimal.new(0)) + end) + end + + def process(%Chain.TokenTransfer{} = transfer) do + preloaded_transfer = preload(transfer) + + summary = fetch_summary(preloaded_transfer.transaction, preloaded_transfer) + + if summary != :nothing do + [summary] + else + [] + end + end + + def process(_), do: nil + + def handle_collection(_transaction, []), do: [] + + def handle_collection(transaction, transfers_list) do + Enum.map( + transfers_list, + fn transfer -> + transaction + |> fetch_summary(transfer) + end + ) + end + + def fetch_summary(%Chain.Transaction{block_number: nil}), do: :nothing + + def fetch_summary(%Chain.Transaction{created_contract_address_hash: nil} = transaction) do + %Summary{ + transaction_hash: transaction.hash, + method: method(transaction), + from_address_hash: transaction.from_address_hash, + to_address_hash: transaction.to_address_hash, + block_number: transaction.block_number, + amount: amount(transaction), + tx_fee: fee(transaction), + name: Explorer.coin_name(), + subject: "Coin transaction", + type: "COIN" + } + end + + def fetch_summary(%Chain.Transaction{to_address_hash: nil} = transaction) do + %Summary{ + transaction_hash: transaction.hash, + method: "contract_creation", + from_address_hash: transaction.from_address_hash, + to_address_hash: transaction.created_contract_address_hash, + block_number: transaction.block_number, + amount: amount(transaction), + tx_fee: fee(transaction), + name: Explorer.coin_name(), + subject: "Contract creation", + type: "COIN" + } + end + + def fetch_summary(_), do: :nothing + + def fetch_summary(%Chain.Transaction{block_number: nil}, _), do: :nothing + + def fetch_summary( + %Chain.Transaction{} = transaction, + %Chain.TokenTransfer{} = transfer + ) do + case transfer.token.type do + "ERC-20" -> + %Summary{ + transaction_hash: transaction.hash, + method: method(transfer), + from_address_hash: transfer.from_address_hash, + to_address_hash: transfer.to_address_hash, + block_number: transfer.block_number, + amount: amount(transfer), + subject: transfer.token.type, + tx_fee: fee(transaction), + name: transfer.token.name, + type: transfer.token.type + } + + "ERC-721" -> + %Summary{ + amount: 0, + transaction_hash: transaction.hash, + method: method(transfer), + from_address_hash: transfer.from_address_hash, + to_address_hash: transfer.to_address_hash, + block_number: transfer.block_number, + subject: to_string(List.first(transfer.token_ids)), + tx_fee: fee(transaction), + name: transfer.token.name, + type: transfer.token.type + } + + "ERC-1155" -> + %Summary{ + amount: 0, + transaction_hash: transaction.hash, + method: method(transfer), + from_address_hash: transfer.from_address_hash, + to_address_hash: transfer.to_address_hash, + block_number: transfer.block_number, + subject: token_ids(transfer), + tx_fee: fee(transaction), + name: transfer.token.name, + type: transfer.token.type + } + end + end + + def fetch_summary(_, _), do: :nothing + + @burn_address "0x0000000000000000000000000000000000000000" + + def method(%{from_address_hash: from, to_address_hash: to}) do + {:ok, burn_address} = format_address(@burn_address) + + cond do + burn_address == from -> "mint" + burn_address == to -> "burn" + true -> "transfer" + end + end + + def format_address(address_hash_string) do + Chain.string_to_address_hash(address_hash_string) + end + + def amount(%Chain.Transaction{} = transaction) do + Wei.to(transaction.value, :ether) + end + + def amount(%Chain.TokenTransfer{amount: amount}) when is_nil(amount), do: nil + + def amount(%Chain.TokenTransfer{amount: amount} = transfer) do + decimals = + Decimal.new( + Integer.pow( + 10, + Decimal.to_integer(token_decimals(transfer)) + ) + ) + + Decimal.div( + amount, + decimals + ) + end + + def token_ids(%Chain.TokenTransfer{token_ids: token_ids}) do + Enum.map_join(token_ids, ", ", fn id -> to_string(id) end) + end + + def token_decimals(%Chain.TokenTransfer{} = transfer) do + transfer.token.decimals || Decimal.new(1) + end + + def type(%Chain.Transaction{}), do: :coin + def type(%Chain.InternalTransaction{}), do: :coin + + def fee(%Chain.Transaction{} = transaction) do + {_, fee} = Chain.fee(transaction, :gwei) + fee + end + + def preload(%Chain.Transaction{} = transaction) do + Repo.preload(transaction, [:internal_transactions, token_transfers: :token]) + end + + def preload(%Chain.TokenTransfer{} = transfer) do + Repo.preload(transfer, [:transaction, :token]) + end + + def preload(_), do: nil +end diff --git a/apps/explorer/lib/explorer/account/notify.ex b/apps/explorer/lib/explorer/account/notify.ex new file mode 100644 index 000000000000..fc1057929465 --- /dev/null +++ b/apps/explorer/lib/explorer/account/notify.ex @@ -0,0 +1,44 @@ +defmodule Explorer.Account.Notify do + @moduledoc """ + Interface for notifier, for import and call from other modules + """ + + alias Explorer.Account + alias Explorer.Account.Notifier.Notify + + require Logger + + def async(transactions) do + Task.async(fn -> process(transactions) end) + end + + defp process(transactions) do + if Account.enabled?() do + check_envs() + Notify.call(transactions) + end + rescue + err -> + Logger.info("--- Notifier error", fetcher: :account) + Logger.info(err, fetcher: :account) + end + + defp check_envs do + check_auth0() + check_sendgrid() + end + + defp check_auth0 do + (Application.get_env(:ueberauth, Ueberauth.Strategy.Auth0.OAuth)[:client_id] && + Application.get_env(:ueberauth, Ueberauth.Strategy.Auth0.OAuth)[:client_secret] && + Application.get_env(:ueberauth, Ueberauth)[:logout_return_to_url] && + Application.get_env(:ueberauth, Ueberauth)[:logout_url]) || + raise "Auth0 not configured" + end + + defp check_sendgrid do + (Application.get_env(:explorer, Explorer.Account)[:sendgrid][:sender] && + Application.get_env(:explorer, Explorer.Account)[:sendgrid][:template]) || + raise "SendGrid not configured" + end +end diff --git a/apps/explorer/lib/explorer/account/public_tags_request.ex b/apps/explorer/lib/explorer/account/public_tags_request.ex new file mode 100644 index 000000000000..eb989e5e7611 --- /dev/null +++ b/apps/explorer/lib/explorer/account/public_tags_request.ex @@ -0,0 +1,255 @@ +defmodule Explorer.Account.PublicTagsRequest do + @moduledoc """ + Module is responsible for requests for public tags + """ + use Explorer.Schema + + alias Ecto.Changeset + alias Explorer.Account.Identity + alias Explorer.Chain.Hash + alias Explorer.Repo + alias Explorer.ThirdPartyIntegrations.AirTable + + import Ecto.Changeset + + @distance_between_same_addresses 24 * 3600 + + @max_public_tags_request_per_account 15 + @max_addresses_per_request 10 + @max_tags_per_request 2 + @max_tag_length 35 + + schema("account_public_tags_requests") do + field(:company, :string) + field(:website, :string) + field(:tags, :string) + field(:addresses, {:array, Hash.Address}) + field(:description, :string) + field(:additional_comment, :string) + field(:request_type, :string) + field(:is_owner, :boolean, default: true) + field(:remove_reason, :string) + field(:request_id, :string) + field(:full_name, Explorer.Encrypted.Binary) + field(:email, Explorer.Encrypted.Binary) + + belongs_to(:identity, Identity) + + timestamps() + end + + @local_fields [:__meta__, :inserted_at, :updated_at, :id, :request_id] + + def to_map(%__MODULE__{} = request) do + association_fields = request.__struct__.__schema__(:associations) + waste_fields = association_fields ++ @local_fields + + network = + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] <> + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] + + request |> Map.from_struct() |> Map.drop(waste_fields) |> Map.put(:network, network) + end + + @attrs ~w(company website description remove_reason request_id)a + @required_attrs ~w(full_name email tags addresses additional_comment request_type is_owner identity_id)a + + def changeset(%__MODULE__{} = public_tags_request, attrs \\ %{}) do + public_tags_request + |> cast(trim_empty_addresses(attrs), @attrs ++ @required_attrs) + |> validate_tags() + |> validate_required(@required_attrs, message: "Required") + |> validate_format(:email, ~r/^[A-Z0-9._%+-]+@[A-Z0-9-]+.+.[A-Z]{2,4}$/i, message: "is invalid") + |> validate_length(:addresses, min: 1, max: @max_addresses_per_request) + |> extract_and_validate_addresses() + |> foreign_key_constraint(:identity_id) + |> public_tags_request_count_constraint() + |> public_tags_request_time_interval_uniqueness() + end + + def changeset_without_constraints(%__MODULE__{} = public_tags_request \\ %__MODULE__{}, attrs \\ %{}) do + public_tags_request + |> cast(attrs, @attrs ++ @required_attrs) + end + + def create(attrs) do + %__MODULE__{} + |> changeset(Map.put(attrs, :request_type, "add")) + |> Repo.account_repo().insert() + |> AirTable.submit() + end + + defp trim_empty_addresses(%{addresses: addresses} = attrs) when is_list(addresses) do + filtered_addresses = Enum.filter(addresses, fn addr -> addr != "" and !is_nil(addr) end) + Map.put(attrs, :addresses, if(filtered_addresses == [], do: [""], else: filtered_addresses)) + end + + defp trim_empty_addresses(attrs), do: attrs + + def public_tags_request_count_constraint(%Changeset{changes: %{identity_id: identity_id}} = request) do + if identity_id + |> public_tags_requests_by_identity_id_query() + |> limit(@max_public_tags_request_per_account) + |> Repo.account_repo().aggregate(:count, :id) >= @max_public_tags_request_per_account do + request + |> add_error(:tags, "Max #{@max_public_tags_request_per_account} public tags requests per account") + else + request + end + end + + def public_tags_request_count_constraint(changeset), do: changeset + + defp public_tags_request_time_interval_uniqueness(%Changeset{changes: %{addresses: addresses}} = request) do + prepared_addresses = + if request.data && request.data.addresses, do: addresses -- request.data.addresses, else: addresses + + public_tags_request = + request + |> fetch_field!(:identity_id) + |> public_tags_requests_by_identity_id_query() + |> where( + [public_tags_request], + fragment("? && ?", public_tags_request.addresses, ^Enum.map(prepared_addresses, fn x -> x.bytes end)) + ) + |> limit(1) + |> Repo.account_repo().one() + + now = DateTime.utc_now() + + if !is_nil(public_tags_request) && + public_tags_request.inserted_at + |> DateTime.add(@distance_between_same_addresses, :second) + |> DateTime.compare(now) == :gt do + request + |> add_error(:addresses, "You have already submitted the same public tag address in the last 24 hours") + else + request + end + end + + defp public_tags_request_time_interval_uniqueness(changeset), do: changeset + + defp extract_and_validate_addresses(%Changeset{} = changeset) do + with {:fetch, {_src, addresses}} <- {:fetch, fetch_field(changeset, :addresses)}, + false <- is_nil(addresses), + {:uniqueness, true} <- {:uniqueness, Enum.count(Enum.uniq(addresses)) == Enum.count(addresses)} do + changeset + else + {:uniqueness, false} -> + add_error(changeset, :addresses, "All addresses should be unique") + + _ -> + add_error(changeset, :addresses, "No addresses") + end + end + + defp validate_tags(%Changeset{} = changeset) do + with {:fetch, {_src, tags}} <- {:fetch, fetch_field(changeset, :tags)}, + false <- is_nil(tags), + trimmed_tags <- String.trim(tags), + tags_list <- String.split(trimmed_tags, ";"), + {:filter_empty, [_ | _] = filtered_tags} <- {:filter_empty, Enum.filter(tags_list, fn tag -> tag != "" end)}, + trimmed_spaces_tags <- Enum.map(filtered_tags, fn tag -> String.trim(tag) end), + {:validate, false} <- {:validate, Enum.any?(tags_list, fn tag -> String.length(tag) > @max_tag_length end)}, + {:uniqueness, true} <- + {:uniqueness, + Enum.count(Enum.uniq_by(trimmed_spaces_tags, &String.downcase(&1))) == Enum.count(trimmed_spaces_tags)}, + trimmed_tags_list <- Enum.take(trimmed_spaces_tags, @max_tags_per_request) do + force_change(changeset, :tags, Enum.join(trimmed_tags_list, ";")) + else + {:uniqueness, false} -> + add_error(changeset, :tags, "All tags should be unique") + + {:filter_empty, _} -> + add_error(changeset, :tags, "All tags are empty strings") + + {:validate, _} -> + add_error(changeset, :tags, "Tags should contain less than #{@max_tag_length} characters") + + _ -> + add_error(changeset, :tags, "No tags") + end + end + + def public_tags_requests_by_identity_id_query(id) when not is_nil(id) do + __MODULE__ + |> where( + [request], + request.identity_id == ^id and request.request_type != "delete" and not is_nil(request.request_id) + ) + |> order_by([request], desc: request.id) + end + + def public_tags_requests_by_identity_id_query(_), do: nil + + def public_tags_request_by_id_and_identity_id_query(id, identity_id) + when not is_nil(id) and not is_nil(identity_id) do + __MODULE__ + |> where([public_tags_request], public_tags_request.identity_id == ^identity_id and public_tags_request.id == ^id) + end + + def public_tags_request_by_id_and_identity_id_query(_, _), do: nil + + def get_public_tags_request_by_id_and_identity_id(id, identity_id) when not is_nil(id) and not is_nil(identity_id) do + id |> public_tags_request_by_id_and_identity_id_query(identity_id) |> Repo.account_repo().one() + end + + def get_public_tags_request_by_id_and_identity_id(_, _), do: nil + + def get_public_tags_requests_by_identity_id(id) when not is_nil(id) do + id + |> public_tags_requests_by_identity_id_query() + |> Repo.account_repo().all() + end + + def get_public_tags_requests_by_identity_id(_), do: nil + + def delete_public_tags_request(identity_id, id) when not is_nil(id) and not is_nil(identity_id) do + id + |> public_tags_request_by_id_and_identity_id_query(identity_id) + |> Repo.account_repo().delete_all() + end + + def delete_public_tags_request(_, _), do: nil + + def update(%{id: id, identity_id: identity_id} = attrs) do + with public_tags_request <- get_public_tags_request_by_id_and_identity_id(id, identity_id), + false <- is_nil(public_tags_request), + {:ok, changeset} <- + public_tags_request |> changeset(Map.put(attrs, :request_type, "edit")) |> Repo.account_repo().update() do + AirTable.submit({:ok, changeset}) + else + true -> + {:error, %{reason: :item_not_found}} + + other -> + other + end + end + + def mark_as_deleted_public_tags_request(%{id: id, identity_id: identity_id, remove_reason: remove_reason}) do + with public_tags_request <- get_public_tags_request_by_id_and_identity_id(id, identity_id), + false <- is_nil(public_tags_request), + {:ok, changeset} <- + public_tags_request + |> changeset_without_constraints(%{request_type: "delete", remove_reason: remove_reason}) + |> Repo.account_repo().update() do + case AirTable.submit({:ok, changeset}) do + {:error, changeset} -> + changeset + + _ -> + true + end + else + {:error, changeset} -> + changeset + + _ -> + false + end + end + + def get_max_public_tags_request_count, do: @max_public_tags_request_per_account +end diff --git a/apps/explorer/lib/explorer/account/tag_address.ex b/apps/explorer/lib/explorer/account/tag_address.ex new file mode 100644 index 000000000000..c3db589648d0 --- /dev/null +++ b/apps/explorer/lib/explorer/account/tag_address.ex @@ -0,0 +1,156 @@ +defmodule Explorer.Account.TagAddress do + @moduledoc """ + Watchlist is root entity for WatchlistAddresses + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Ecto.Changeset + alias Explorer.Account.Identity + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Address, Hash} + + import Explorer.Chain, only: [hash_to_lower_case_string: 1] + + @max_tag_address_per_account 15 + + schema "account_tag_addresses" do + field(:address_hash_hash, Cloak.Ecto.SHA256) + field(:name, Explorer.Encrypted.Binary) + field(:address_hash, Explorer.Encrypted.AddressHash) + + belongs_to(:identity, Identity) + + timestamps() + end + + @attrs ~w(name identity_id address_hash)a + + def changeset do + %__MODULE__{} + |> cast(%{}, @attrs) + end + + @doc false + def changeset(tag, attrs) do + tag + |> cast(attrs, @attrs) + |> validate_required(@attrs, message: "Required") + |> validate_length(:name, min: 1, max: 35) + |> put_hashed_fields() + |> unique_constraint([:identity_id, :address_hash_hash], message: "Address tag already exists") + |> check_existance_or_create_address() + |> tag_address_count_constraint() + end + + def create(attrs) do + %__MODULE__{} + |> changeset(attrs) + |> Repo.account_repo().insert() + end + + defp put_hashed_fields(changeset) do + changeset + |> put_change(:address_hash_hash, hash_to_lower_case_string(get_field(changeset, :address_hash))) + end + + defp check_existance_or_create_address(%Changeset{changes: %{address_hash: address_hash}, valid?: true} = changeset) do + check_existance_or_create_address_inner(changeset, address_hash) + end + + defp check_existance_or_create_address(changeset), do: changeset + + defp check_existance_or_create_address_inner(changeset, address_hash) do + with {:ok, hash} <- Hash.Address.cast(address_hash), + {:ok, %Address{}} <- Chain.find_or_insert_address_from_hash(hash, []) do + changeset + end + end + + def tag_address_count_constraint(%Changeset{changes: %{identity_id: identity_id}} = tag_address) do + if identity_id + |> tags_address_by_identity_id_query() + |> limit(@max_tag_address_per_account) + |> Repo.account_repo().aggregate(:count, :id) >= @max_tag_address_per_account do + tag_address + |> add_error(:name, "Max #{@max_tag_address_per_account} tags per account") + else + tag_address + end + end + + def tag_address_count_constraint(changeset), do: changeset + + def tags_address_by_identity_id_query(id) when not is_nil(id) do + __MODULE__ + |> where([tag], tag.identity_id == ^id) + |> order_by([tag], desc: tag.id) + end + + def tags_address_by_identity_id_query(_), do: nil + + def get_tags_address_by_identity_id(id) when not is_nil(id) do + id + |> tags_address_by_identity_id_query() + |> Repo.account_repo().all() + end + + def get_tags_address_by_identity_id(_), do: nil + + def tag_address_by_address_hash_and_identity_id_query(address_hash, identity_id) + when not is_nil(address_hash) and not is_nil(identity_id) do + __MODULE__ + |> where([tag], tag.identity_id == ^identity_id and tag.address_hash == ^address_hash) + end + + def tag_address_by_address_hash_and_identity_id_query(_, _), do: nil + + def get_tag_address_by_address_hash_and_identity_id(address_hash, identity_id) + when not is_nil(address_hash) and not is_nil(identity_id) do + address_hash + |> hash_to_lower_case_string() + |> tag_address_by_address_hash_and_identity_id_query(identity_id) + |> Repo.account_repo().one() + end + + def get_tag_address_by_address_hash_and_identity_id(_, _), do: nil + + def tag_address_by_id_and_identity_id_query(tag_id, identity_id) + when not is_nil(tag_id) and not is_nil(identity_id) do + __MODULE__ + |> where([tag], tag.identity_id == ^identity_id and tag.id == ^tag_id) + end + + def tag_address_by_id_and_identity_id_query(_, _), do: nil + + def get_tag_address_by_id_and_identity_id_query(tag_id, identity_id) + when not is_nil(tag_id) and not is_nil(identity_id) do + tag_id + |> tag_address_by_id_and_identity_id_query(identity_id) + |> Repo.account_repo().one() + end + + def get_tag_address_by_id_and_identity_id_query(_, _), do: nil + + def delete(tag_id, identity_id) when not is_nil(tag_id) and not is_nil(identity_id) do + tag_id + |> tag_address_by_id_and_identity_id_query(identity_id) + |> Repo.account_repo().delete_all() + end + + def delete(_, _), do: nil + + def update(%{id: tag_id, identity_id: identity_id} = attrs) do + with tag <- get_tag_address_by_id_and_identity_id_query(tag_id, identity_id), + false <- is_nil(tag) do + tag |> changeset(attrs) |> Repo.account_repo().update() + else + true -> + {:error, %{reason: :item_not_found}} + end + end + + def get_max_tags_count, do: @max_tag_address_per_account +end diff --git a/apps/explorer/lib/explorer/account/tag_transaction.ex b/apps/explorer/lib/explorer/account/tag_transaction.ex new file mode 100644 index 000000000000..2839b2355960 --- /dev/null +++ b/apps/explorer/lib/explorer/account/tag_transaction.ex @@ -0,0 +1,161 @@ +defmodule Explorer.Account.TagTransaction do + @moduledoc """ + This is a personal tag for transaction + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Ecto.Changeset + alias Explorer.Account.Identity + alias Explorer.{Chain, Repo} + import Explorer.Chain, only: [hash_to_lower_case_string: 1] + + @max_tag_transaction_per_account 15 + + schema "account_tag_transactions" do + field(:tx_hash_hash, Cloak.Ecto.SHA256) + field(:name, Explorer.Encrypted.Binary) + field(:tx_hash, Explorer.Encrypted.TransactionHash) + + belongs_to(:identity, Identity) + + timestamps() + end + + @attrs ~w(name identity_id tx_hash)a + + def changeset do + %__MODULE__{} + |> cast(%{}, @attrs) + end + + @doc false + def changeset(tag, attrs) do + tag + |> cast(attrs, @attrs) + |> validate_required(@attrs, message: "Required") + |> validate_length(:name, min: 1, max: 35) + |> put_hashed_fields() + |> unique_constraint([:identity_id, :tx_hash_hash], message: "Transaction tag already exists") + |> tag_transaction_count_constraint() + |> check_transaction_existance() + end + + def create(attrs) do + %__MODULE__{} + |> changeset(attrs) + |> Repo.account_repo().insert() + end + + defp put_hashed_fields(changeset) do + changeset + |> put_change(:tx_hash_hash, hash_to_lower_case_string(get_field(changeset, :tx_hash))) + end + + defp check_transaction_existance(%Changeset{changes: %{tx_hash: tx_hash}} = changeset) do + check_transaction_existance_inner(changeset, tx_hash) + end + + defp check_transaction_existance(changeset), do: changeset + + defp check_transaction_existance_inner(changeset, tx_hash) do + if match?({:ok, _}, Chain.hash_to_transaction(tx_hash)) do + changeset + else + add_error(changeset, :tx_hash, "Transaction does not exist") + end + end + + def tag_transaction_count_constraint(%Changeset{changes: %{identity_id: identity_id}} = tag_transaction) do + if identity_id + |> tags_transaction_by_identity_id_query() + |> limit(@max_tag_transaction_per_account) + |> Repo.account_repo().aggregate(:count, :id) >= @max_tag_transaction_per_account do + tag_transaction + |> add_error(:name, "Max #{@max_tag_transaction_per_account} tags per account") + else + tag_transaction + end + end + + def tag_transaction_count_constraint(changeset), do: changeset + + def tags_transaction_by_identity_id_query(id) when not is_nil(id) do + __MODULE__ + |> where([tag], tag.identity_id == ^id) + |> order_by([tag], desc: tag.id) + end + + def tags_transaction_by_identity_id_query(_), do: nil + + def get_tags_transaction_by_identity_id(id) when not is_nil(id) do + id + |> tags_transaction_by_identity_id_query() + |> Repo.account_repo().all() + end + + def get_tags_transaction_by_identity_id(_), do: nil + + def tag_transaction_by_transaction_hash_and_identity_id_query(tx_hash, identity_id) + when not is_nil(tx_hash) and not is_nil(identity_id) do + __MODULE__ + |> where([tag], tag.identity_id == ^identity_id and tag.tx_hash == ^tx_hash) + end + + def tag_transaction_by_transaction_hash_and_identity_id_query(_, _), do: nil + + def get_tag_transaction_by_transaction_hash_and_identity_id(tx_hash, identity_id) + when not is_nil(tx_hash) and not is_nil(identity_id) do + tx_hash + |> hash_to_lower_case_string() + |> tag_transaction_by_transaction_hash_and_identity_id_query(identity_id) + |> Repo.account_repo().one() + end + + def get_tag_transaction_by_transaction_hash_and_identity_id(_, _), do: nil + + def tag_transaction_by_id_and_identity_id_query(tag_id, identity_id) + when not is_nil(tag_id) and not is_nil(identity_id) do + __MODULE__ + |> where([tag], tag.identity_id == ^identity_id and tag.id == ^tag_id) + end + + def tag_transaction_by_id_and_identity_id_query(_, _), do: nil + + def get_tag_transaction_by_id_and_identity_id_query(tag_id, identity_id) + when not is_nil(tag_id) and not is_nil(identity_id) do + tag_id + |> tag_transaction_by_id_and_identity_id_query(identity_id) + |> Repo.account_repo().one() + end + + def get_tag_transaction_by_id_and_identity_id_query(_, _), do: nil + + def delete(tag_id, identity_id) when not is_nil(tag_id) and not is_nil(identity_id) do + tag_id + |> tag_transaction_by_id_and_identity_id_query(identity_id) + |> Repo.account_repo().delete_all() + end + + def delete(_, _), do: nil + + def update(%{id: tag_id, identity_id: identity_id} = attrs) do + with tag <- get_tag_transaction_by_id_and_identity_id_query(tag_id, identity_id), + false <- is_nil(tag) do + tag |> changeset(attrs) |> Repo.account_repo().update() + else + true -> + {:error, %{reason: :item_not_found}} + end + end + + def get_max_tags_count, do: @max_tag_transaction_per_account +end + +defimpl Jason.Encoder, for: Explorer.Account.TagTransaction do + def encode(tx_tag, opts) do + Jason.Encode.string(tx_tag.name, opts) + end +end diff --git a/apps/explorer/lib/explorer/account/watchlist.ex b/apps/explorer/lib/explorer/account/watchlist.ex new file mode 100644 index 000000000000..cd6998b83f77 --- /dev/null +++ b/apps/explorer/lib/explorer/account/watchlist.ex @@ -0,0 +1,27 @@ +defmodule Explorer.Account.Watchlist do + @moduledoc """ + Watchlist is root entity for WatchlistAddresses + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Explorer.Account.{Identity, WatchlistAddress} + + @derive {Jason.Encoder, only: [:name, :watchlist_addresses]} + schema "account_watchlists" do + field(:name, :string) + belongs_to(:identity, Identity) + has_many(:watchlist_addresses, WatchlistAddress) + + timestamps() + end + + @doc false + def changeset(watchlist, attrs) do + watchlist + |> cast(attrs, [:name]) + |> validate_required([:name]) + end +end diff --git a/apps/explorer/lib/explorer/account/watchlist_address.ex b/apps/explorer/lib/explorer/account/watchlist_address.ex new file mode 100644 index 000000000000..17a10cf63bf8 --- /dev/null +++ b/apps/explorer/lib/explorer/account/watchlist_address.ex @@ -0,0 +1,176 @@ +defmodule Explorer.Account.WatchlistAddress do + @moduledoc """ + WatchlistAddress entity + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Ecto.Changeset + alias Explorer.Account.Notifier.ForbiddenAddress + alias Explorer.Account.Watchlist + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Address, Wei} + + import Explorer.Chain, only: [hash_to_lower_case_string: 1] + + @max_watchlist_addresses_per_account 10 + + schema "account_watchlist_addresses" do + field(:address_hash_hash, Cloak.Ecto.SHA256) + field(:name, Explorer.Encrypted.Binary) + field(:address_hash, Explorer.Encrypted.AddressHash) + + belongs_to(:watchlist, Watchlist) + + field(:watch_coin_input, :boolean, default: true) + field(:watch_coin_output, :boolean, default: true) + field(:watch_erc_20_input, :boolean, default: true) + field(:watch_erc_20_output, :boolean, default: true) + field(:watch_erc_721_input, :boolean, default: true) + field(:watch_erc_721_output, :boolean, default: true) + field(:watch_erc_1155_input, :boolean, default: true) + field(:watch_erc_1155_output, :boolean, default: true) + field(:notify_email, :boolean, default: true) + field(:notify_epns, :boolean) + field(:notify_feed, :boolean) + field(:notify_inapp, :boolean) + + field(:fetched_coin_balance, Wei, virtual: true) + + timestamps() + end + + @attrs ~w(name address_hash watch_coin_input watch_coin_output watch_erc_20_input watch_erc_20_output watch_erc_721_input watch_erc_721_output watch_erc_1155_input watch_erc_1155_output notify_email notify_epns notify_feed notify_inapp watchlist_id)a + + def changeset do + %__MODULE__{} + |> cast(%{}, @attrs) + end + + @doc false + def changeset(watchlist_address, attrs \\ %{}) do + watchlist_address + |> cast(attrs, @attrs) + |> validate_length(:name, min: 1, max: 35) + |> validate_required([:name, :address_hash, :watchlist_id], message: "Required") + |> put_hashed_fields() + |> unique_constraint([:watchlist_id, :address_hash_hash], + name: "unique_watchlist_id_address_hash_hash_index", + message: "Address already added to the watch list" + ) + |> check_address() + |> watchlist_address_count_constraint() + end + + defp put_hashed_fields(changeset) do + changeset + |> put_change(:address_hash_hash, hash_to_lower_case_string(get_field(changeset, :address_hash))) + end + + def create(attrs) do + %__MODULE__{} + |> changeset(attrs) + |> Repo.account_repo().insert() + end + + def watchlist_address_count_constraint(%Changeset{changes: %{watchlist_id: watchlist_id}} = watchlist_address) do + if watchlist_id + |> watchlist_addresses_by_watchlist_id_query() + |> limit(@max_watchlist_addresses_per_account) + |> Repo.account_repo().aggregate(:count, :id) >= @max_watchlist_addresses_per_account do + watchlist_address + |> add_error(:name, "Max #{@max_watchlist_addresses_per_account} watch list addresses per account") + else + watchlist_address + end + end + + def watchlist_address_count_constraint(changeset), do: changeset + + defp check_address(%Changeset{changes: %{address_hash: address_hash}, valid?: true} = changeset) do + check_address_inner(changeset, address_hash) + end + + defp check_address(%Changeset{data: %{address_hash: address_hash}, valid?: true} = changeset) do + check_address_inner(changeset, address_hash) + end + + defp check_address(changeset), do: changeset + + defp check_address_inner(changeset, address_hash) do + with {:ok, address_hash} <- ForbiddenAddress.check(address_hash), + {:ok, %Address{}} <- Chain.find_or_insert_address_from_hash(address_hash, []) do + changeset + else + {:error, reason} -> + add_error(changeset, :address_hash, reason) + + _ -> + add_error(changeset, :address_hash, "Address error") + end + end + + def watchlist_addresses_by_watchlist_id_query(watchlist_id) when not is_nil(watchlist_id) do + __MODULE__ + |> where([wl_address], wl_address.watchlist_id == ^watchlist_id) + end + + def watchlist_addresses_by_watchlist_id_query(_), do: nil + + def watchlist_address_by_id_and_watchlist_id_query(watchlist_address_id, watchlist_id) + when not is_nil(watchlist_address_id) and not is_nil(watchlist_id) do + __MODULE__ + |> where([wl_address], wl_address.watchlist_id == ^watchlist_id and wl_address.id == ^watchlist_address_id) + end + + def watchlist_address_by_id_and_watchlist_id_query(_, _), do: nil + + def get_watchlist_address_by_id_and_watchlist_id(watchlist_address_id, watchlist_id) + when not is_nil(watchlist_address_id) and not is_nil(watchlist_id) do + watchlist_address_id + |> watchlist_address_by_id_and_watchlist_id_query(watchlist_id) + |> Repo.account_repo().one() + end + + def get_watchlist_address_by_id_and_watchlist_id(_, _), do: nil + + def delete(watchlist_address_id, watchlist_id) + when not is_nil(watchlist_address_id) and not is_nil(watchlist_id) do + watchlist_address_id + |> watchlist_address_by_id_and_watchlist_id_query(watchlist_id) + |> Repo.account_repo().delete_all() + end + + def delete(_, _), do: nil + + def update(%{id: id, watchlist_id: watchlist_id} = attrs) do + with watchlist_address <- get_watchlist_address_by_id_and_watchlist_id(id, watchlist_id), + false <- is_nil(watchlist_address) do + watchlist_address + |> changeset(attrs) + |> Repo.account_repo().update() + else + true -> + {:error, %{reason: :item_not_found}} + end + end + + def get_max_watchlist_addresses_count, do: @max_watchlist_addresses_per_account + + def preload_address_fetched_coin_balance(%Watchlist{watchlist_addresses: watchlist_addresses} = watchlist) do + w_addresses = + Enum.map(watchlist_addresses, fn wa -> + preload_address_fetched_coin_balance(wa) + end) + + %Watchlist{watchlist | watchlist_addresses: w_addresses} + end + + def preload_address_fetched_coin_balance(%__MODULE__{address_hash: address_hash} = watchlist_address) do + %__MODULE__{watchlist_address | fetched_coin_balance: address_hash |> Address.fetched_coin_balance() |> Repo.one()} + end + + def preload_address_fetched_coin_balance(watchlist), do: watchlist +end diff --git a/apps/explorer/lib/explorer/account/watchlist_notification.ex b/apps/explorer/lib/explorer/account/watchlist_notification.ex new file mode 100644 index 000000000000..6c909be3ed3a --- /dev/null +++ b/apps/explorer/lib/explorer/account/watchlist_notification.ex @@ -0,0 +1,65 @@ +defmodule Explorer.Account.WatchlistNotification do + @moduledoc """ + Strored notification about event + related to WatchlistAddress + """ + + use Explorer.Schema + + import Ecto.Changeset + import Explorer.Chain, only: [hash_to_lower_case_string: 1] + + alias Explorer.Account.WatchlistAddress + + schema "account_watchlist_notifications" do + field(:amount, :decimal) + field(:block_number, :integer) + field(:direction, :string) + field(:method, :string) + field(:tx_fee, :decimal) + field(:type, :string) + field(:viewed_at, :integer) + field(:name, Explorer.Encrypted.Binary) + field(:subject, Explorer.Encrypted.Binary) + field(:subject_hash, Cloak.Ecto.SHA256) + + belongs_to(:watchlist_address, WatchlistAddress) + + field(:from_address_hash, Explorer.Encrypted.AddressHash) + field(:to_address_hash, Explorer.Encrypted.AddressHash) + field(:transaction_hash, Explorer.Encrypted.TransactionHash) + + field(:from_address_hash_hash, Cloak.Ecto.SHA256) + field(:to_address_hash_hash, Cloak.Ecto.SHA256) + field(:transaction_hash_hash, Cloak.Ecto.SHA256) + + timestamps() + end + + @doc false + def changeset(watchlist_notifications, attrs) do + watchlist_notifications + |> cast(attrs, [:amount, :direction, :name, :type, :method, :block_number, :tx_fee, :value, :decimals, :viewed_at]) + |> validate_required([ + :amount, + :direction, + :name, + :type, + :method, + :block_number, + :tx_fee, + :value, + :decimals, + :viewed_at + ]) + |> put_hashed_fields() + end + + defp put_hashed_fields(changeset) do + changeset + |> put_change(:from_address_hash_hash, hash_to_lower_case_string(get_field(changeset, :from_address_hash))) + |> put_change(:to_address_hash_hash, hash_to_lower_case_string(get_field(changeset, :to_address_hash))) + |> put_change(:transaction_hash_hash, hash_to_lower_case_string(get_field(changeset, :transaction_hash))) + |> put_change(:subject_hash, get_field(changeset, :subject)) + end +end diff --git a/apps/explorer/lib/explorer/accounts/accounts.ex b/apps/explorer/lib/explorer/accounts/accounts.ex index b399d3781659..079b8309cf35 100644 --- a/apps/explorer/lib/explorer/accounts/accounts.ex +++ b/apps/explorer/lib/explorer/accounts/accounts.ex @@ -3,7 +3,7 @@ defmodule Explorer.Accounts do Entrypoint for modifying user account information. """ - alias Comeonin.Bcrypt + alias Bcrypt alias Ecto.Changeset alias Explorer.Accounts.User alias Explorer.Accounts.User.{Authenticate, Registration} @@ -61,7 +61,7 @@ defmodule Explorer.Accounts do with {:ok, authentication} <- authentication, {:user, %User{} = user} <- {:user, Repo.get_by(User, username: authentication.username)}, - {:password, true} <- {:password, Bcrypt.checkpw(authentication.password, user.password_hash)} do + {:password, true} <- {:password, Bcrypt.verify_pass(authentication.password, user.password_hash)} do {:ok, user} else {:error, %Changeset{}} = error -> @@ -69,7 +69,7 @@ defmodule Explorer.Accounts do {:user, nil} -> # Run dummy check to mitigate timing attacks - Bcrypt.dummy_checkpw() + Bcrypt.no_user_verify() {:error, :invalid_credentials} {:password, false} -> diff --git a/apps/explorer/lib/explorer/accounts/user.ex b/apps/explorer/lib/explorer/accounts/user.ex index 1a3b46456b05..fd797d0b9532 100644 --- a/apps/explorer/lib/explorer/accounts/user.ex +++ b/apps/explorer/lib/explorer/accounts/user.ex @@ -7,7 +7,7 @@ defmodule Explorer.Accounts.User do import Ecto.Changeset - alias Comeonin.Bcrypt + alias Bcrypt alias Ecto.Changeset alias Explorer.Accounts.{User, UserContact} @@ -42,7 +42,7 @@ defmodule Explorer.Accounts.User do defp hash_password(%Changeset{} = changeset) do if password = get_change(changeset, :password) do - put_change(changeset, :password_hash, Bcrypt.hashpwsalt(password)) + put_change(changeset, :password_hash, Bcrypt.hash_pwd_salt(password)) else changeset end diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index c42370c0e5e0..191840d0d363 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -5,7 +5,7 @@ defmodule Explorer.Application do use Application - alias Explorer.Admin + alias Explorer.{Admin, TokenTransferTokenIdMigration} alias Explorer.Chain.Cache.{ Accounts, @@ -20,6 +20,7 @@ defmodule Explorer.Application do NetVersion, Transaction, Transactions, + TransactionsApiV2, Uncles } @@ -43,6 +44,8 @@ defmodule Explorer.Application do base_children = [ Explorer.Repo, Explorer.Repo.Replica1, + Explorer.Repo.Account, + Explorer.Vault, Supervisor.child_spec({SpandexDatadog.ApiServer, datadog_opts()}, id: SpandexDatadog.ApiServer), Supervisor.child_spec({Task.Supervisor, name: Explorer.HistoryTaskSupervisor}, id: Explorer.HistoryTaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.MarketTaskSupervisor}, id: Explorer.MarketTaskSupervisor), @@ -64,8 +67,10 @@ defmodule Explorer.Application do con_cache_child_spec(MarketHistoryCache.cache_name()), con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)), Transactions, + TransactionsApiV2, Accounts, - Uncles + Uncles, + {Redix, redix_opts()} ] children = base_children ++ configurable_children() @@ -82,6 +87,10 @@ defmodule Explorer.Application do configure(Explorer.KnownTokens), configure(Explorer.Market.History.Cataloger), configure(Explorer.Chain.Cache.TokenExchangeRate), + configure(Explorer.Chain.Cache.ContractsCounter), + configure(Explorer.Chain.Cache.NewContractsCounter), + configure(Explorer.Chain.Cache.VerifiedContractsCounter), + configure(Explorer.Chain.Cache.NewVerifiedContractsCounter), configure(Explorer.Chain.Transaction.History.Historian), configure(Explorer.Chain.Events.Listener), configure(Explorer.Counters.AddressesWithBalanceCounter), @@ -97,7 +106,9 @@ defmodule Explorer.Application do configure(Explorer.Counters.AverageBlockTime), configure(Explorer.Counters.Bridge), configure(Explorer.Validator.MetadataProcessor), - configure(MinMissingBlockNumber) + configure(Explorer.Tags.AddressTag.Cataloger), + configure(MinMissingBlockNumber), + configure(TokenTransferTokenIdMigration.Supervisor) ] |> List.flatten() end @@ -174,4 +185,8 @@ defmodule Explorer.Application do id: {ConCache, name} ) end + + defp redix_opts do + {System.get_env("ACCOUNT_REDIS_URL") || "redis://127.0.0.1:6379", [name: :redix]} + end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 79cf9e1b95a0..74cb630f06d4 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -5,19 +5,22 @@ defmodule Explorer.Chain do import Ecto.Query, only: [ + dynamic: 1, + dynamic: 2, from: 2, join: 4, + join: 5, limit: 2, lock: 2, offset: 2, order_by: 2, order_by: 3, preload: 2, + preload: 3, select: 2, select: 3, subquery: 1, union: 2, - update: 2, where: 2, where: 3 ] @@ -29,10 +32,9 @@ defmodule Explorer.Chain do alias ABI.TypeDecoder alias Ecto.{Changeset, Multi} - alias EthereumJSONRPC.Contract alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction - alias Explorer.Counters.LastFetchedCounter + alias Explorer.Counters.{LastFetchedCounter, TokenHoldersCounter, TokenTransfersCounter} alias Explorer.Chain @@ -66,21 +68,50 @@ defmodule Explorer.Chain do Accounts, BlockNumber, Blocks, + ContractsCounter, + NewContractsCounter, + NewVerifiedContractsCounter, Transactions, - Uncles + Uncles, + VerifiedContractsCounter } alias Explorer.Chain.Import.Runner alias Explorer.Chain.InternalTransaction.{CallType, Type} - alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter} + + alias Explorer.Counters.{ + AddressesCounter, + AddressesWithBalanceCounter, + AddressTokenTransfersCounter, + AddressTransactionsCounter, + AddressTransactionsGasUsageCounter + } + alias Explorer.Market.MarketHistoryCache alias Explorer.{PagingOptions, Repo} - alias Explorer.SmartContract.{Helper, Reader} + alias Explorer.SmartContract.Helper alias Dataloader.Ecto, as: DataloaderEcto @default_paging_options %PagingOptions{page_size: 50} + @token_transfers_per_transaction_preview 10 + @token_transfers_neccessity_by_association %{ + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + token: :required + } + + @method_name_to_id_map %{ + "approve" => "095ea7b3", + "transfer" => "a9059cbb", + "multicall" => "5ae401dc", + "mint" => "40c10f19", + "commit" => "f14fcbc8" + } + @max_incoming_transactions_count 10_000 @revert_msg_prefix_1 "Revert: " @@ -159,7 +190,7 @@ defmodule Explorer.Chain do def address_estimated_count do cached_value = AddressesCounter.fetch() - if is_nil(cached_value) do + if is_nil(cached_value) || cached_value == 0 do %Postgrex.Result{rows: [[count]]} = Repo.query!("SELECT reltuples FROM pg_class WHERE relname = 'addresses';") count @@ -188,10 +219,7 @@ defmodule Explorer.Chain do while to have the return back. """ def count_addresses do - Repo.one( - Address.count(), - timeout: :infinity - ) + Repo.aggregate(Address, :count, timeout: :infinity) end @doc """ @@ -251,19 +279,11 @@ defmodule Explorer.Chain do |> common_where_limit_order(paging_options) |> wrapped_union_subquery() - full_query = - query_to_address_hash_wrapped - |> union(^query_from_address_hash_wrapped) - |> union(^query_created_contract_address_hash_wrapped) - - full_query + query_to_address_hash_wrapped + |> union(^query_from_address_hash_wrapped) + |> union(^query_created_contract_address_hash_wrapped) |> wrapped_union_subquery() - |> order_by( - [q], - desc: q.block_number, - desc: q.transaction_index, - desc: q.index - ) + |> common_where_limit_order(paging_options) |> preload(transaction: :block) |> join_associations(necessity_by_association) |> Repo.all() @@ -432,7 +452,7 @@ defmodule Explorer.Chain do options |> Keyword.get(:paging_options, @default_paging_options) - |> fetch_transactions(from_block, to_block) + |> fetch_transactions(from_block, to_block, true) end defp transactions_block_numbers_at_address(address_hash, options) do @@ -548,6 +568,20 @@ defmodule Explorer.Chain do |> Repo.all() end + @spec address_hash_to_token_transfers_new(Hash.Address.t() | String.t(), Keyword.t()) :: [TokenTransfer.t()] + def address_hash_to_token_transfers_new(address_hash, options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + direction = Keyword.get(options, :direction) + filters = Keyword.get(options, :token_type) + necessity_by_association = Keyword.get(options, :necessity_by_association) + + direction + |> TokenTransfer.token_transfers_by_address_hash(address_hash, filters) + |> join_associations(necessity_by_association) + |> TokenTransfer.handle_paging_options(paging_options) + |> Repo.all() + end + @doc """ address_hash_to_token_transfers_including_contract/2 function returns token transfers on address (to/from/contract). It is used by CSV export of token transfers button. @@ -625,7 +659,7 @@ defmodule Explorer.Chain do {block_number, transaction_index, log_index} = paging_options.key || {BlockNumber.get_max(), 0, 0} - base_query = + base = from(log in Log, inner_join: transaction in Transaction, on: transaction.hash == log.transaction_hash, @@ -640,6 +674,10 @@ defmodule Explorer.Chain do select: log ) + base_query = + base + |> filter_topic(options) + wrapped_query = from( log in subquery(base_query), @@ -653,7 +691,6 @@ defmodule Explorer.Chain do ) wrapped_query - |> filter_topic(options) |> where_block_number_in_period(from_block, to_block) |> Repo.all() |> Enum.take(paging_options.page_size) @@ -682,9 +719,7 @@ defmodule Explorer.Chain do end def where_block_number_in_period(base_query, from_block, to_block) when is_nil(from_block) and is_nil(to_block) do - from(q in base_query, - where: 1 - ) + base_query end def where_block_number_in_period(base_query, from_block, to_block) do @@ -794,19 +829,40 @@ defmodule Explorer.Chain do end end - @uncle_reward_coef 1 / 32 - def block_reward_by_parts(block, transactions) do - %{hash: block_hash, number: block_number} = block - base_fee_per_gas = Map.get(block, :base_fee_per_gas) + def txn_fees(transactions) do + Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc -> + gas_used + |> Decimal.new() + |> Decimal.mult(gas_price_to_decimal(gas_price)) + |> Decimal.add(acc) + end) + end - txn_fees = - Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc -> + defp gas_price_to_decimal(%Wei{} = wei), do: wei.value + defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price) + + def burned_fees(transactions, base_fee_per_gas) do + burned_fee_counter = + transactions + |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc -> gas_used |> Decimal.new() - |> Decimal.mult(Decimal.new(gas_price)) |> Decimal.add(acc) end) + base_fee_per_gas && Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), burned_fee_counter) + end + + defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei + defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} + + @uncle_reward_coef 1 / 32 + def block_reward_by_parts(block, transactions) do + %{hash: block_hash, number: block_number} = block + base_fee_per_gas = Map.get(block, :base_fee_per_gas) + + txn_fees = txn_fees(transactions) + static_reward = Repo.one( from( @@ -816,17 +872,9 @@ defmodule Explorer.Chain do ) ) || %Wei{value: Decimal.new(0)} - burned_fee_counter = - transactions - |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc -> - gas_used - |> Decimal.new() - |> Decimal.add(acc) - end) - has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles) - burned_fees = base_fee_per_gas && Wei.mult(%Wei{value: Decimal.new(base_fee_per_gas)}, burned_fee_counter) + burned_fees = burned_fees(transactions, base_fee_per_gas) uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil %{ @@ -887,8 +935,10 @@ defmodule Explorer.Chain do `:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than the `index` that are passed. """ - @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()] - def block_to_transactions(block_hash, options \\ []) when is_list(options) do + @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option], true | false) :: [ + Transaction.t() + ] + def block_to_transactions(block_hash, options \\ [], old_ui? \\ true) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) options @@ -897,8 +947,12 @@ defmodule Explorer.Chain do |> join(:inner, [transaction], block in assoc(transaction, :block)) |> where([_, block], block.hash == ^block_hash) |> join_associations(necessity_by_association) - |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) + |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> Repo.all() + |> (&if(old_ui?, + do: &1, + else: Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_neccessity_by_association) end) + )).() end @doc """ @@ -988,6 +1042,17 @@ defmodule Explorer.Chain do Repo.aggregate(to_address_query, :count, :hash, timeout: :infinity) end + @spec address_hash_to_transaction_count(Hash.Address.t()) :: non_neg_integer() + def address_hash_to_transaction_count(address_hash) do + query = + from( + transaction in Transaction, + where: transaction.to_address_hash == ^address_hash or transaction.from_address_hash == ^address_hash + ) + + Repo.aggregate(query, :count, :hash, timeout: :infinity) + end + @spec address_to_incoming_transaction_gas_usage(Hash.Address.t()) :: Decimal.t() | nil def address_to_incoming_transaction_gas_usage(address_hash) do to_address_query = @@ -1046,8 +1111,8 @@ defmodule Explorer.Chain do iex> Explorer.Chain.confirmations(block, block_height: 0) {:ok, 1} """ - @spec confirmations(Block.t(), [{:block_height, block_height()}]) :: - {:ok, non_neg_integer()} | {:error, :non_consensus} + @spec confirmations(Block.t() | nil, [{:block_height, block_height()}]) :: + {:ok, non_neg_integer()} | {:error, :non_consensus | :pending} def confirmations(%Block{consensus: true, number: number}, named_arguments) when is_list(named_arguments) do max_consensus_block_number = Keyword.fetch!(named_arguments, :block_height) @@ -1057,6 +1122,8 @@ defmodule Explorer.Chain do def confirmations(%Block{consensus: false}, _), do: {:error, :non_consensus} + def confirmations(nil, _), do: {:error, :pending} + @doc """ Creates an address. @@ -1186,39 +1253,59 @@ defmodule Explorer.Chain do @doc """ Checks to see if the chain is down indexing based on the transaction from the - oldest block and the `fetch_internal_transactions` pending operation + oldest block and the pending operation """ - @spec finished_indexing?() :: boolean() - def finished_indexing? do - json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) - variant = Keyword.fetch!(json_rpc_named_arguments, :variant) + @spec finished_internal_transactions_indexing?() :: boolean() + def finished_internal_transactions_indexing? do + internal_transactions_disabled? = System.get_env("INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER", "false") == "true" - if variant == EthereumJSONRPC.Ganache || variant == EthereumJSONRPC.Arbitrum do + if internal_transactions_disabled? do true else - with {:transactions_exist, true} <- {:transactions_exist, Repo.exists?(Transaction)}, - min_block_number when not is_nil(min_block_number) <- Repo.aggregate(Transaction, :min, :block_number) do - min_block_number = - min_block_number - |> Decimal.max(EthereumJSONRPC.first_block_to_fetch(:trace_first_block)) - |> Decimal.to_integer() - - query = - from( - b in Block, - join: pending_ops in assoc(b, :pending_operations), - where: pending_ops.fetch_internal_transactions, - where: b.consensus and b.number == ^min_block_number - ) + json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) + variant = Keyword.fetch!(json_rpc_named_arguments, :variant) - !Repo.exists?(query) + if variant == EthereumJSONRPC.Ganache || variant == EthereumJSONRPC.Arbitrum do + true else - {:transactions_exist, false} -> true - nil -> false + with {:transactions_exist, true} <- {:transactions_exist, Repo.exists?(Transaction)}, + min_block_number when not is_nil(min_block_number) <- Repo.aggregate(Transaction, :min, :block_number) do + min_block_number = + min_block_number + |> Decimal.max(EthereumJSONRPC.first_block_to_fetch(:trace_first_block)) + |> Decimal.to_integer() + + query = + from( + b in Block, + join: pending_ops in assoc(b, :pending_operations), + where: b.consensus and b.number == ^min_block_number + ) + + !Repo.exists?(query) + else + {:transactions_exist, false} -> true + nil -> false + end end end end + def finished_blocks_indexing?(indexed_ratio_blocks) do + Decimal.compare(indexed_ratio_blocks, 1) !== :lt + end + + @doc """ + Checks if indexing of blocks and internal transactions finished aka full indexing + """ + @spec finished_indexing?(Decimal.t()) :: boolean() + def finished_indexing?(indexed_ratio_blocks) do + case finished_blocks_indexing?(indexed_ratio_blocks) do + false -> false + _ -> Chain.finished_internal_transactions_indexing?() + end + end + @doc """ The `t:Explorer.Chain.Transaction.t/0` `gas_price` of the `transaction` in `unit`. """ @@ -1296,7 +1383,10 @@ defmodule Explorer.Chain do address_verified_twin_contract_updated = address_verified_twin_contract |> Map.put(:address_hash, hash) - |> Map.put_new(:metadata_from_verified_twin, true) + |> Map.put(:metadata_from_verified_twin, true) + |> Map.put(:implementation_address_hash, nil) + |> Map.put(:implementation_name, nil) + |> Map.put(:implementation_fetched_at, nil) address_result |> Map.put(:smart_contract, address_verified_twin_contract_updated) @@ -1472,7 +1562,7 @@ defmodule Explorer.Chain do _ -> case Integer.parse(term) do - {block_number, _} -> + {block_number, ""} -> from(block in Block, where: block.number == ^block_number, select: %{ @@ -1494,7 +1584,9 @@ defmodule Explorer.Chain do end end - def joint_search(paging_options, offset, string) do + def joint_search(paging_options, offset, raw_string) do + string = String.trim(raw_string) + case prepare_search_term(string) do {:some, term} -> tokens_query = search_token_query(term) @@ -1812,7 +1904,10 @@ defmodule Explorer.Chain do address_verified_twin_contract_updated = address_verified_twin_contract |> Map.put(:address_hash, hash) - |> Map.put_new(:metadata_from_verified_twin, true) + |> Map.put(:metadata_from_verified_twin, true) + |> Map.put(:implementation_address_hash, nil) + |> Map.put(:implementation_name, nil) + |> Map.put(:implementation_fetched_at, nil) address_result |> Map.put(:smart_contract, address_verified_twin_contract_updated) @@ -2015,6 +2110,33 @@ defmodule Explorer.Chain do end end + # preload_to_detect_tt?: we don't need to preload more than one token transfer in case the tx inside the list (we dont't show any token transfers on tx tile in new UI) + def preload_token_transfers( + %Transaction{hash: tx_hash, block_hash: block_hash} = transaction, + necessity_by_association, + preload_to_detect_tt? \\ true + ) do + token_transfers = + TokenTransfer + |> (&if(is_nil(block_hash), + do: where(&1, [token_transfer], token_transfer.transaction_hash == ^tx_hash), + else: + where( + &1, + [token_transfer], + token_transfer.transaction_hash == ^tx_hash and token_transfer.block_hash == ^block_hash + ) + )).() + |> limit(^if(preload_to_detect_tt?, do: 1, else: @token_transfers_per_transaction_preview + 1)) + |> order_by([token_transfer], asc: token_transfer.log_index) + |> join_associations(necessity_by_association) + |> Repo.all() + + %Transaction{transaction | token_transfers: token_transfers} + end + + def get_token_transfers_per_transaction_preview_count, do: @token_transfers_per_transaction_preview + @doc """ Converts list of `t:Explorer.Chain.Transaction.t/0` `hashes` to the list of `t:Explorer.Chain.Transaction.t/0`s for those `hashes`. @@ -2071,27 +2193,60 @@ defmodule Explorer.Chain do ...> insert(:block, number: index) ...> Process.sleep(200) ...> end - iex> Explorer.Chain.indexed_ratio() + iex> Explorer.Chain.indexed_ratio_blocks() Decimal.new(1, 50, -2) If there are no blocks, the percentage is 0. - iex> Explorer.Chain.indexed_ratio() + iex> Explorer.Chain.indexed_ratio_blocks() Decimal.new(0) """ - @spec indexed_ratio() :: Decimal.t() - def indexed_ratio do + @spec indexed_ratio_blocks() :: Decimal.t() + def indexed_ratio_blocks do %{min: min, max: max} = BlockNumber.get_all() + min_blockchain_block_number = + case Integer.parse(Application.get_env(:indexer, :first_block)) do + {block_number, _} -> block_number + _ -> 0 + end + case {min, max} do {0, 0} -> Decimal.new(0) _ -> - result = Decimal.div(max - min + 1, max + 1) + result = Decimal.div(max - min + 1, max - min_blockchain_block_number + 1) - Decimal.round(result, 2, :down) + result + |> Decimal.round(2, :down) + |> Decimal.min(Decimal.new(1)) + end + end + + @spec indexed_ratio_internal_transactions() :: Decimal.t() + def indexed_ratio_internal_transactions do + %{max: max} = BlockNumber.get_all() + count = Repo.aggregate(PendingBlockOperation, :count, timeout: :infinity) + + min_blockchain_trace_block_number = + case Integer.parse(Application.get_env(:indexer, :trace_first_block)) do + {block_number, _} -> block_number + _ -> 0 + end + + case max do + 0 -> + Decimal.new(0) + + _ -> + full_blocks_range = max - min_blockchain_trace_block_number + 1 + result = Decimal.div(full_blocks_range - count, full_blocks_range) + + result + |> Decimal.round(2, :down) + |> Decimal.min(Decimal.new(1)) end end @@ -2320,8 +2475,14 @@ defmodule Explorer.Chain do query = if filter && filter !== "" do - base_query_with_paging - |> where(fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^filter)) + case prepare_search_term(filter) do + {:some, filter_term} -> + base_query_with_paging + |> where(fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^filter_term)) + + _ -> + base_query_with_paging + end else base_query_with_paging end @@ -2448,17 +2609,7 @@ defmodule Explorer.Chain do @spec address_to_transaction_count(Address.t()) :: non_neg_integer() def address_to_transaction_count(address) do - if contract?(address) do - incoming_transaction_count = address_to_incoming_transaction_count(address.hash) - - if incoming_transaction_count == 0 do - total_transactions_sent_by_address(address.hash) - else - incoming_transaction_count - end - else - total_transactions_sent_by_address(address.hash) - end + address_hash_to_transaction_count(address.hash) end @spec address_to_token_transfer_count(Address.t()) :: non_neg_integer() @@ -2496,6 +2647,15 @@ defmodule Explorer.Chain do @doc """ Return the balance in usd corresponding to this token. Return nil if the usd_value of the token is not present. """ + def balance_in_usd(_token_balance, %{usd_value: nil}) do + nil + end + + def balance_in_usd(token_balance, %{usd_value: usd_value, decimals: decimals}) do + tokens = CurrencyHelpers.divide_decimals(token_balance.value, decimals) + Decimal.mult(tokens, usd_value) + end + def balance_in_usd(%{token: %{usd_value: nil}}) do nil end @@ -2506,17 +2666,6 @@ defmodule Explorer.Chain do Decimal.mult(tokens, price) end - def address_tokens_usd_sum(token_balances) do - token_balances - |> Enum.reduce(Decimal.new(0), fn {token_balance, _, _}, acc -> - if token_balance.value && token_balance.token.usd_value do - Decimal.add(acc, balance_in_usd(token_balance)) - else - acc - end - end) - end - defp contract?(%{contract_code: nil}), do: false defp contract?(%{contract_code: _}), do: true @@ -2582,11 +2731,9 @@ defmodule Explorer.Chain do Only blocks with consensus are returned. iex> non_consensus = insert(:block, consensus: false) - iex> insert(:pending_block_operation, block: non_consensus, fetch_internal_transactions: true) + iex> insert(:pending_block_operation, block: non_consensus) iex> unfetched = insert(:block) - iex> insert(:pending_block_operation, block: unfetched, fetch_internal_transactions: true) - iex> fetched = insert(:block) - iex> insert(:pending_block_operation, block: fetched, fetch_internal_transactions: false) + iex> insert(:pending_block_operation, block: unfetched) iex> {:ok, number_set} = Explorer.Chain.stream_blocks_with_unfetched_internal_transactions( ...> MapSet.new(), ...> fn number, acc -> @@ -2597,8 +2744,6 @@ defmodule Explorer.Chain do false iex> unfetched.number in number_set true - iex> fetched.hash in number_set - false """ @spec stream_blocks_with_unfetched_internal_transactions( @@ -2611,7 +2756,6 @@ defmodule Explorer.Chain do from( b in Block, join: pending_ops in assoc(b, :pending_operations), - where: pending_ops.fetch_internal_transactions, where: b.consensus, select: b.number ) @@ -2888,11 +3032,6 @@ defmodule Explorer.Chain do def fetch_min_missing_block_cache do max_block_number = BlockNumber.get_max() - min_missing_block_number = - "min_missing_block_number" - |> Chain.get_last_fetched_counter() - |> Decimal.to_integer() - if max_block_number > 0 do query = from(b in Block, @@ -2900,11 +3039,10 @@ defmodule Explorer.Chain do missing_range in fragment( """ (SELECT b1.number - FROM generate_series((?)::integer, (?)::integer) AS b1(number) + FROM generate_series(0, (?)::integer) AS b1(number) WHERE NOT EXISTS (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus)) """, - ^min_missing_block_number, ^max_block_number ), on: b.number == missing_range.number, @@ -2918,6 +3056,19 @@ defmodule Explorer.Chain do end end + def remove_blocks_consensus(block_numbers) do + numbers = List.wrap(block_numbers) + + query = + from( + block in Block, + where: block.number in ^numbers, + where: block.consensus + ) + + Repo.update_all(query, set: [consensus: false]) + end + @doc """ Calculates the ranges of missing consensus blocks in `range`. @@ -2987,10 +3138,14 @@ defmodule Explorer.Chain do right_join: missing_range in fragment( """ - (SELECT distinct b1.number + ( + SELECT distinct b1.number FROM generate_series((?)::integer, (?)::integer) AS b1(number) WHERE NOT EXISTS - (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus)) + (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus) + ORDER BY b1.number DESC + LIMIT 500000 + ) """, ^range_min, ^range_max @@ -3170,7 +3325,7 @@ defmodule Explorer.Chain do iex> newest_first_transactions = 50 |> insert_list(:transaction) |> with_block() |> Enum.reverse() iex> oldest_seen = Enum.at(newest_first_transactions, 9) iex> paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}} - iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions(paging_options: paging_options) + iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions(true, paging_options: paging_options) iex> length(recent_collated_transactions) 10 iex> hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash @@ -3186,26 +3341,23 @@ defmodule Explorer.Chain do the `block_number` and `index` that are passed. """ - @spec recent_collated_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()] - def recent_collated_transactions(options \\ []) when is_list(options) do + @spec recent_collated_transactions(true | false, [paging_options | necessity_by_association_option]) :: [ + Transaction.t() + ] + def recent_collated_transactions(old_ui?, options \\ []) + when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - if is_nil(paging_options.key) do - paging_options.page_size - |> Transactions.take_enough() - |> case do - nil -> - transactions = fetch_recent_collated_transactions(paging_options, necessity_by_association) - Transactions.update(transactions) - transactions - - transactions -> - transactions - end - else - fetch_recent_collated_transactions(paging_options, necessity_by_association) - end + method_id_filter = Keyword.get(options, :method) + type_filter = Keyword.get(options, :type) + + fetch_recent_collated_transactions( + old_ui?, + paging_options, + necessity_by_association, + method_id_filter, + type_filter + ) end # RAP - random access pagination @@ -3263,13 +3415,25 @@ defmodule Explorer.Chain do |> Repo.aggregate(:count, :hash) end - def fetch_recent_collated_transactions(paging_options, necessity_by_association) do + def fetch_recent_collated_transactions( + old_ui?, + paging_options, + necessity_by_association, + method_id_filter, + type_filter + ) do paging_options |> fetch_transactions() |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index)) + |> apply_filter_by_method_id_to_transactions(method_id_filter) + |> apply_filter_by_tx_type_to_transactions(type_filter) |> join_associations(necessity_by_association) - |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) + |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> Repo.all() + |> (&if(old_ui?, + do: &1, + else: Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_neccessity_by_association) end) + )).() end @doc """ @@ -3296,19 +3460,30 @@ defmodule Explorer.Chain do Results will be the transactions older than the `inserted_at` and `hash` that are passed. """ - @spec recent_pending_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()] - def recent_pending_transactions(options \\ []) when is_list(options) do + @spec recent_pending_transactions([paging_options | necessity_by_association_option], true | false) :: [ + Transaction.t() + ] + def recent_pending_transactions(options \\ [], old_ui? \\ true) + when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options, @default_paging_options) + method_id_filter = Keyword.get(options, :method) + type_filter = Keyword.get(options, :type) Transaction |> page_pending_transaction(paging_options) |> limit(^paging_options.page_size) |> pending_transactions_query() - |> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash) + |> apply_filter_by_method_id_to_transactions(method_id_filter) + |> apply_filter_by_tx_type_to_transactions(type_filter) + |> order_by([transaction], desc: transaction.inserted_at, asc: transaction.hash) |> join_associations(necessity_by_association) - |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) + |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> Repo.all() + |> (&if(old_ui?, + do: &1, + else: Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_neccessity_by_association) end) + )).() end def pending_transactions_query(query) do @@ -3534,6 +3709,7 @@ defmodule Explorer.Chain do def transaction_to_token_transfers(transaction_hash, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = options |> Keyword.get(:paging_options, @default_paging_options) |> Map.put(:asc_order, true) + token_type = Keyword.get(options, :token_type) TokenTransfer |> join(:inner, [token_transfer], transaction in assoc(token_transfer, :transaction)) @@ -3542,6 +3718,9 @@ defmodule Explorer.Chain do transaction.hash == ^transaction_hash and token_transfer.block_hash == transaction.block_hash and token_transfer.block_number == transaction.block_number ) + |> join(:inner, [tt], token in assoc(tt, :token), as: :token) + |> preload([token: token], [{:token, token}]) + |> TokenTransfer.filter_by_type(token_type) |> TokenTransfer.page_token_transfer(paging_options) |> limit(^paging_options.page_size) |> order_by([token_transfer], asc: token_transfer.log_index) @@ -4050,6 +4229,30 @@ defmodule Explorer.Chain do |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name]) end + def get_verified_twin_contract(%Explorer.Chain.Address{} = target_address) do + case target_address do + %{contract_code: %Chain.Data{bytes: contract_code_bytes}} -> + target_address_hash = target_address.hash + + contract_code_md5 = Helper.contract_code_md5(contract_code_bytes) + + verified_contract_twin_query = + from( + smart_contract in SmartContract, + where: smart_contract.contract_code_md5 == ^contract_code_md5, + where: smart_contract.address_hash != ^target_address_hash, + select: smart_contract, + limit: 1 + ) + + verified_contract_twin_query + |> Repo.one(timeout: 10_000) + + _ -> + nil + end + end + @doc """ Finds metadata for verification of a contract from verified twins: contracts with the same bytecode which were verified previously, returns a single t:SmartContract.t/0 @@ -4063,24 +4266,8 @@ defmodule Explorer.Chain do def get_address_verified_twin_contract(%Explorer.Chain.Hash{} = address_hash) do with target_address <- Repo.get(Address, address_hash), - false <- is_nil(target_address), - %{contract_code: %Chain.Data{bytes: contract_code_bytes}} <- target_address do - target_address_hash = target_address.hash - - contract_code_md5 = Helper.contract_code_md5(contract_code_bytes) - - verified_contract_twin_query = - from( - smart_contract in SmartContract, - where: smart_contract.contract_code_md5 == ^contract_code_md5, - where: smart_contract.address_hash != ^target_address_hash, - select: smart_contract, - limit: 1 - ) - - verified_contract_twin = - verified_contract_twin_query - |> Repo.one(timeout: 10_000) + false <- is_nil(target_address) do + verified_contract_twin = get_verified_twin_contract(target_address) verified_contract_twin_additional_sources = get_contract_additional_sources(verified_contract_twin) @@ -4173,13 +4360,29 @@ defmodule Explorer.Chain do Chain.get_address_verified_twin_contract(address_hash).verified_contract if address_verified_twin_contract do - Map.put(address_verified_twin_contract, :address_hash, address_hash) + address_verified_twin_contract + |> Map.put(:address_hash, address_hash) + |> Map.put(:metadata_from_verified_twin, true) + |> Map.put(:implementation_address_hash, nil) + |> Map.put(:implementation_name, nil) + |> Map.put(:implementation_fetched_at, nil) else current_smart_contract end end end + @spec address_hash_to_smart_contract_without_twin(Hash.Address.t()) :: SmartContract.t() | nil + def address_hash_to_smart_contract_without_twin(address_hash) do + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + Repo.one(query) + end + def smart_contract_fully_verified?(address_hash_str) when is_binary(address_hash_str) do case string_to_address_hash(address_hash_str) do {:ok, address_hash} -> @@ -4230,13 +4433,28 @@ defmodule Explorer.Chain do if Repo.one(query), do: true, else: false end - defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil) do + defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, is_address? \\ false) do Transaction - |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) + |> order_for_transactions(is_address?) |> where_block_number_in_period(from_block, to_block) |> handle_paging_options(paging_options) end + defp order_for_transactions(query, true) do + query + |> order_by([transaction], + desc: transaction.block_number, + desc: transaction.index, + desc: transaction.inserted_at, + asc: transaction.hash + ) + end + + defp order_for_transactions(query, _) do + query + |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) + end + defp fetch_transactions_in_ascending_order_by_index(paging_options) do Transaction |> order_by([transaction], desc: transaction.block_number, asc: transaction.index) @@ -4253,12 +4471,22 @@ defmodule Explorer.Chain do defp handle_paging_options(query, nil), do: query + defp handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query + defp handle_paging_options(query, paging_options) do query |> page_transaction(paging_options) |> limit(^paging_options.page_size) end + defp handle_verified_contracts_paging_options(query, nil), do: query + + defp handle_verified_contracts_paging_options(query, paging_options) do + query + |> page_verified_contracts(paging_options) + |> limit(^paging_options.page_size) + end + defp handle_token_transfer_paging_options(query, nil), do: query defp handle_token_transfer_paging_options(query, paging_options) do @@ -4333,6 +4561,16 @@ defmodule Explorer.Chain do end end + defp join_association(query, association, necessity) do + case necessity do + :optional -> + preload(query, ^association) + + :required -> + from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}]) + end + end + defp join_associations(query, necessity_by_association) when is_map(necessity_by_association) do Enum.reduce(necessity_by_association, query, fn {association, join}, acc_query -> join_association(acc_query, association, join) @@ -4425,7 +4663,10 @@ defmodule Explorer.Chain do where( query, [transaction], - transaction.inserted_at < ^inserted_at or (transaction.inserted_at == ^inserted_at and transaction.hash < ^hash) + (is_nil(transaction.block_number) and + (transaction.inserted_at < ^inserted_at or + (transaction.inserted_at == ^inserted_at and transaction.hash > ^hash))) or + not is_nil(transaction.block_number) ) end @@ -4458,6 +4699,20 @@ defmodule Explorer.Chain do defp page_search_results(query, %PagingOptions{key: nil}), do: query + defp page_search_results(query, %PagingOptions{ + key: {_address_hash, _tx_hash, _block_hash, holder_count, name, inserted_at, item_type} + }) + when holder_count in [nil, ""] do + where( + query, + [item], + (item.name > ^name and item.type == ^item_type) or + (item.name == ^name and item.inserted_at < ^inserted_at and + item.type == ^item_type) or + item.type != ^item_type + ) + end + # credo:disable-for-next-line defp page_search_results(query, %PagingOptions{ key: {_address_hash, _tx_hash, _block_hash, holder_count, name, inserted_at, item_type} @@ -4496,6 +4751,12 @@ defmodule Explorer.Chain do ) end + defp page_verified_contracts(query, %PagingOptions{key: nil}), do: query + + defp page_verified_contracts(query, %PagingOptions{key: {id}}) do + where(query, [contract], contract.id < ^id) + end + @doc """ Ensures the following conditions are true: @@ -4581,17 +4842,29 @@ defmodule Explorer.Chain do select: token.contract_address_hash ) - query = + token_ids_query = from( token_transfer in TokenTransfer, + select: %{ + token_contract_address_hash: token_transfer.token_contract_address_hash, + token_id: fragment("unnest(?)", token_transfer.token_ids) + } + ) + + query = + from( + transfer in subquery(token_ids_query), inner_join: token in subquery(nft_tokens), - on: token.contract_address_hash == token_transfer.token_contract_address_hash, + on: token.contract_address_hash == transfer.token_contract_address_hash, left_join: instance in Instance, on: - token_transfer.token_id == instance.token_id and - token_transfer.token_contract_address_hash == instance.token_contract_address_hash, - where: is_nil(instance.token_id) and not is_nil(token_transfer.token_id), - select: %{contract_address_hash: token_transfer.token_contract_address_hash, token_id: token_transfer.token_id} + transfer.token_contract_address_hash == instance.token_contract_address_hash and + transfer.token_id == instance.token_id, + where: is_nil(instance.token_id), + select: %{ + contract_address_hash: transfer.token_contract_address_hash, + token_id: transfer.token_id + } ) distinct_query = @@ -4896,17 +5169,11 @@ defmodule Explorer.Chain do |> Repo.all() end - @spec erc721_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t()) :: - {:ok, TokenTransfer.t()} | {:error, :not_found} - def erc721_token_instance_from_token_id_and_token_address(token_id, token_contract_address) do + @spec erc721_or_erc1155_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t()) :: + {:ok, Instance.t()} | {:error, :not_found} + def erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, token_contract_address) do query = - from(tt in TokenTransfer, - left_join: instance in Instance, - on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id, - where: tt.token_contract_address_hash == ^token_contract_address and tt.token_id == ^token_id, - limit: 1, - select: %{tt | instance: instance} - ) + from(i in Instance, where: i.token_contract_address_hash == ^token_contract_address and i.token_id == ^token_id) case Repo.one(query) do nil -> {:error, :not_found} @@ -5015,14 +5282,20 @@ defmodule Explorer.Chain do end end + def get_token_balance(address_hash, token_contract_address_hash, block_number) do + query = TokenBalance.fetch_token_balance(address_hash, token_contract_address_hash, block_number) + + Repo.one(query) + end + def get_coin_balance(address_hash, block_number) do query = CoinBalance.fetch_coin_balance(address_hash, block_number) Repo.one(query) end - @spec address_to_balances_by_day(Hash.Address.t()) :: [balance_by_day] - def address_to_balances_by_day(address_hash) do + @spec address_to_balances_by_day(Hash.Address.t(), true | false) :: [balance_by_day] + def address_to_balances_by_day(address_hash, api? \\ false) do latest_block_timestamp = address_hash |> CoinBalance.last_coin_balance_timestamp() @@ -5033,7 +5306,7 @@ defmodule Explorer.Chain do |> Repo.all() |> Enum.sort_by(fn %{date: d} -> {d.year, d.month, d.day} end) |> replace_last_value(latest_block_timestamp) - |> normalize_balances_by_day() + |> normalize_balances_by_day(api?) end # https://github.com/blockscout/blockscout/issues/2658 @@ -5043,12 +5316,12 @@ defmodule Explorer.Chain do defp replace_last_value(items, _), do: items - defp normalize_balances_by_day(balances_by_day) do + defp normalize_balances_by_day(balances_by_day, api?) do result = balances_by_day |> Enum.filter(fn day -> day.value end) - |> Enum.map(fn day -> Map.update!(day, :date, &to_string(&1)) end) - |> Enum.map(fn day -> Map.update!(day, :value, &Wei.to(&1, :ether)) end) + |> (&if(api?, do: &1, else: Enum.map(&1, fn day -> Map.update!(day, :date, fn x -> to_string(x) end) end))).() + |> (&if(api?, do: &1, else: Enum.map(&1, fn day -> Map.update!(day, :value, fn x -> Wei.to(x, :ether) end) end))).() today = Date.to_string(NaiveDateTime.utc_now()) @@ -5108,15 +5381,23 @@ defmodule Explorer.Chain do Repo.one!(query, timeout: :infinity) end - @spec address_to_unique_tokens(Hash.Address.t(), [paging_options]) :: [TokenTransfer.t()] + @spec address_to_unique_tokens(Hash.Address.t(), [paging_options]) :: [Instance.t()] def address_to_unique_tokens(contract_address_hash, options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) contract_address_hash - |> TokenTransfer.address_to_unique_tokens() - |> TokenTransfer.page_token_transfer(paging_options) + |> Instance.address_to_unique_token_instances() + |> Instance.page_token_instance(paging_options) |> limit(^paging_options.page_size) |> Repo.all() + |> Enum.map(fn instance -> + owner = + instance + |> Instance.owner_query() + |> Repo.one() + + %{instance | owner: owner} + end) end @spec data() :: Dataloader.Ecto.t() @@ -5538,59 +5819,58 @@ defmodule Explorer.Chain do end @doc """ - Checks if a `t:Explorer.Chain.TokenTransfer.t/0` with the given `hash` and `token_id` exists. + Checks if a `t:Explorer.Chain.Token.Instance.t/0` with the given `hash` and `token_id` exists. Returns `:ok` if found - iex> contract_address = insert(:address) + iex> token = insert(:token) iex> token_id = 10 - iex> insert(:token_transfer, - ...> from_address: contract_address, - ...> token_contract_address: contract_address, + iex> insert(:token_instance, + ...> token_contract_address_hash: token.contract_address_hash, ...> token_id: token_id ...> ) - iex> Explorer.Chain.check_erc721_token_instance_exists(token_id, contract_address.hash) + iex> Explorer.Chain.check_erc721_or_erc1155_token_instance_exists(token_id, token.contract_address_hash) :ok Returns `:not_found` if not found iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.check_erc721_token_instance_exists(10, hash) + iex> Explorer.Chain.check_erc721_or_erc1155_token_instance_exists(10, hash) :not_found """ - @spec check_erc721_token_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) :: :ok | :not_found - def check_erc721_token_instance_exists(token_id, hash) do + @spec check_erc721_or_erc1155_token_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) :: + :ok | :not_found + def check_erc721_or_erc1155_token_instance_exists(token_id, hash) do token_id - |> erc721_token_instance_exist?(hash) + |> erc721_or_erc1155_token_instance_exist?(hash) |> boolean_to_check_result() end @doc """ - Checks if a `t:Explorer.Chain.TokenTransfer.t/0` with the given `hash` and `token_id` exists. + Checks if a `t:Explorer.Chain.Token.Instance.t/0` with the given `hash` and `token_id` exists. Returns `true` if found - iex> contract_address = insert(:address) + iex> token = insert(:token) iex> token_id = 10 - iex> insert(:token_transfer, - ...> from_address: contract_address, - ...> token_contract_address: contract_address, + iex> insert(:token_instance, + ...> token_contract_address_hash: token.contract_address_hash, ...> token_id: token_id ...> ) - iex> Explorer.Chain.erc721_token_instance_exist?(token_id, contract_address.hash) + iex> Explorer.Chain.erc721_or_erc1155_token_instance_exist?(token_id, token.contract_address_hash) true Returns `false` if not found iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.erc721_token_instance_exist?(10, hash) + iex> Explorer.Chain.erc721_or_erc1155_token_instance_exist?(10, hash) false """ - @spec erc721_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() - def erc721_token_instance_exist?(token_id, hash) do + @spec erc721_or_erc1155_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() + def erc721_or_erc1155_token_instance_exist?(token_id, hash) do query = - from(tt in TokenTransfer, - where: tt.token_contract_address_hash == ^hash and tt.token_id == ^token_id + from(i in Instance, + where: i.token_contract_address_hash == ^hash and i.token_id == ^Decimal.new(token_id) ) Repo.exists?(query) @@ -5601,7 +5881,7 @@ defmodule Explorer.Chain do defp boolean_to_check_result(false), do: :not_found @doc """ - Fetches the first trace from the Parity trace URL. + Fetches the first trace from the Nethermind trace URL. """ def fetch_first_trace(transactions_params, json_rpc_named_arguments) do case EthereumJSONRPC.fetch_first_trace(transactions_params, json_rpc_named_arguments) do @@ -5616,32 +5896,16 @@ defmodule Explorer.Chain do end end - def combine_proxy_implementation_abi(proxy_address_hash, abi) when not is_nil(abi) do - implementation_abi = get_implementation_abi_from_proxy(proxy_address_hash, abi) + def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract) when not is_nil(abi) do + implementation_abi = get_implementation_abi_from_proxy(smart_contract) if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi end - def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do + def combine_proxy_implementation_abi(_) do [] end - def proxy_contract?(address_hash, abi) when not is_nil(abi) do - implementation_method_abi = - abi - |> Enum.find(fn method -> - Map.get(method, "name") == "implementation" || - master_copy_pattern?(method) - end) - - if implementation_method_abi || - get_implementation_address_hash_eip_1967(address_hash) !== "0x0000000000000000000000000000000000000000", - do: true, - else: false - end - - def proxy_contract?(_address_hash, abi) when is_nil(abi), do: false - def gnosis_safe_contract?(abi) when not is_nil(abi) do implementation_method_abi = abi @@ -5654,167 +5918,7 @@ defmodule Explorer.Chain do def gnosis_safe_contract?(abi) when is_nil(abi), do: false - @spec get_implementation_address_hash(Hash.Address.t(), list()) :: {String.t() | nil, String.t() | nil} - def get_implementation_address_hash(proxy_address_hash, abi) - when not is_nil(proxy_address_hash) and not is_nil(abi) do - implementation_method_abi = - abi - |> Enum.find(fn method -> - Map.get(method, "name") == "implementation" && Map.get(method, "stateMutability") == "view" - end) - - master_copy_method_abi = - abi - |> Enum.find(fn method -> - master_copy_pattern?(method) - end) - - implementation_address = - cond do - implementation_method_abi -> - get_implementation_address_hash_basic(proxy_address_hash, abi) - - master_copy_method_abi -> - get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) - - true -> - get_implementation_address_hash_eip_1967(proxy_address_hash) - end - - save_implementation_name(implementation_address, proxy_address_hash) - end - - def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do - {nil, nil} - end - - defp get_implementation_address_hash_eip_1967(proxy_address_hash) do - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - # https://eips.ethereum.org/EIPS/eip-1967 - storage_slot_logic_contract_address = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" - - {_status, implementation_address} = - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_logic_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> - fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) - - {:ok, implementation_logic_address} -> - {:ok, implementation_logic_address} - - {:error, _} -> - {:ok, "0x"} - end - - abi_decode_address_output(implementation_address) - end - - # changes requested by https://github.com/blockscout/blockscout/issues/4770 - # for support BeaconProxy pattern - defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do - # https://eips.ethereum.org/EIPS/eip-1967 - storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" - - implementation_method_abi = [ - %{ - "type" => "function", - "stateMutability" => "view", - "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], - "name" => "implementation", - "inputs" => [] - } - ] - - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_beacon_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> - fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) - - {:ok, beacon_contract_address} -> - case beacon_contract_address - |> abi_decode_address_output() - |> get_implementation_address_hash_basic(implementation_method_abi) do - <> -> - {:ok, implementation_address} - - _ -> - {:ok, beacon_contract_address} - end - - {:error, _} -> - {:ok, "0x"} - end - end - - # changes requested by https://github.com/blockscout/blockscout/issues/5292 - defp fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do - # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation" - storage_slot_logic_contract_address = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" - - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_logic_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> - {:ok, "0x"} - - {:ok, logic_contract_address} -> - {:ok, logic_contract_address} - - {:error, _} -> - {:ok, "0x"} - end - end - - defp get_implementation_address_hash_basic(proxy_address_hash, abi) do - # 5c60da1b = keccak256(implementation()) - implementation_address = - case Reader.query_contract( - proxy_address_hash, - abi, - %{ - "5c60da1b" => [] - }, - false - ) do - %{"5c60da1b" => {:ok, [result]}} -> result - _ -> nil - end - - address_to_hex(implementation_address) - end - - defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - master_copy_storage_pointer = "0x0" - - {:ok, implementation_address} = - Contract.eth_get_storage_at_request( - proxy_address_hash, - master_copy_storage_pointer, - nil, - json_rpc_named_arguments - ) - - abi_decode_address_output(implementation_address) - end - - defp master_copy_pattern?(method) do + def master_copy_pattern?(method) do Map.get(method, "type") == "constructor" && method |> Enum.find(fn item -> @@ -5835,57 +5939,6 @@ defmodule Explorer.Chain do end) end - defp save_implementation_name(empty_address_hash_string, _) - when empty_address_hash_string in [ - "0x", - "0x0", - "0x0000000000000000000000000000000000000000000000000000000000000000", - @burn_address_hash_str - ], - do: {empty_address_hash_string, nil} - - defp save_implementation_name(implementation_address_hash_string, proxy_address_hash) - when is_binary(implementation_address_hash_string) do - with {:ok, address_hash} <- string_to_address_hash(implementation_address_hash_string), - %SmartContract{name: name} <- address_hash_to_smart_contract(address_hash) do - SmartContract - |> where([sc], sc.address_hash == ^proxy_address_hash) - |> update(set: [implementation_name: ^name]) - |> Repo.update_all([]) - - {implementation_address_hash_string, name} - else - _ -> - {implementation_address_hash_string, nil} - end - end - - defp save_implementation_name(other, _), do: {other, nil} - - defp abi_decode_address_output(nil), do: nil - - defp abi_decode_address_output("0x"), do: @burn_address_hash_str - - defp abi_decode_address_output(address) when is_binary(address) do - if String.length(address) > 42 do - "0x" <> String.slice(address, -40, 40) - else - address - end - end - - defp abi_decode_address_output(_), do: nil - - defp address_to_hex(address) do - if address do - if String.starts_with?(address, "0x") do - address - else - "0x" <> Base.encode16(address, case: :lower) - end - end - end - def get_implementation_abi(implementation_address_hash_string) when not is_nil(implementation_address_hash_string) do case Chain.string_to_address_hash(implementation_address_hash_string) do {:ok, implementation_address_hash} -> @@ -5909,15 +5962,13 @@ defmodule Explorer.Chain do [] end - def get_implementation_abi_from_proxy(proxy_address_hash, abi) + def get_implementation_abi_from_proxy(%SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract) when not is_nil(proxy_address_hash) and not is_nil(abi) do - {implementation_address_hash_string, _name} = get_implementation_address_hash(proxy_address_hash, abi) + {implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract) get_implementation_abi(implementation_address_hash_string) end - def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do - [] - end + def get_implementation_abi_from_proxy(_), do: [] defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do {:ok, to_address_hash} = @@ -6122,4 +6173,322 @@ defmodule Explorer.Chain do query |> Repo.one() end + + def is_address_hash_is_smart_contract?(nil), do: false + + def is_address_hash_is_smart_contract?(address_hash) do + with %Address{contract_code: bytecode} <- Repo.get_by(Address, hash: address_hash), + false <- is_nil(bytecode) do + true + else + _ -> + false + end + end + + def hash_to_lower_case_string(hash) do + hash + |> to_string() + |> String.downcase() + end + + def recent_transactions(options, [:pending | _]) do + recent_pending_transactions(options, false) + end + + def recent_transactions(options, _) do + recent_collated_transactions(false, options) + end + + def apply_filter_by_method_id_to_transactions(query, nil), do: query + + def apply_filter_by_method_id_to_transactions(query, filter) when is_list(filter) do + method_ids = Enum.flat_map(filter, &map_name_or_method_id_to_method_id/1) + + if method_ids != [] do + query + |> where([tx], fragment("SUBSTRING(? FOR 4)", tx.input) in ^method_ids) + else + query + end + end + + def apply_filter_by_method_id_to_transactions(query, filter), + do: apply_filter_by_method_id_to_transactions(query, [filter]) + + defp map_name_or_method_id_to_method_id(string) when is_binary(string) do + if id = @method_name_to_id_map[string] do + decode_method_id(id) + else + trimmed = + string + |> String.replace("0x", "", global: false) + + decode_method_id(trimmed) + end + end + + defp decode_method_id(method_id) when is_binary(method_id) do + case String.length(method_id) == 8 && Base.decode16(method_id, case: :mixed) do + {:ok, bytes} -> + [bytes] + + _ -> + [] + end + end + + def apply_filter_by_tx_type_to_transactions(query, [_ | _] = filter) do + {dynamic, modified_query} = apply_filter_by_tx_type_to_transactions_inner(filter, query) + + modified_query + |> where(^dynamic) + end + + def apply_filter_by_tx_type_to_transactions(query, _filter), do: query + + def apply_filter_by_tx_type_to_transactions_inner(dynamic \\ dynamic(false), filter, query) + + def apply_filter_by_tx_type_to_transactions_inner(dynamic, [type | remain], query) do + case type do + :contract_call -> + dynamic + |> filter_contract_call_dynamic() + |> apply_filter_by_tx_type_to_transactions_inner( + remain, + join(query, :inner, [tx], address in assoc(tx, :to_address), as: :to_address) + ) + + :contract_creation -> + dynamic + |> filter_contract_creation_dynamic() + |> apply_filter_by_tx_type_to_transactions_inner(remain, query) + + :coin_transfer -> + dynamic + |> filter_transaction_dynamic() + |> apply_filter_by_tx_type_to_transactions_inner(remain, query) + + :token_transfer -> + dynamic + |> filter_token_transfer_dynamic() + |> apply_filter_by_tx_type_to_transactions_inner(remain, query) + + :token_creation -> + dynamic + |> filter_token_creation_dynamic() + |> apply_filter_by_tx_type_to_transactions_inner( + remain, + join(query, :inner, [tx], token in Token, + on: token.contract_address_hash == tx.created_contract_address_hash, + as: :created_token + ) + ) + end + end + + def apply_filter_by_tx_type_to_transactions_inner(dynamic_query, _, query), do: {dynamic_query, query} + + def filter_contract_creation_dynamic(dynamic) do + dynamic([tx], ^dynamic or is_nil(tx.to_address_hash)) + end + + def filter_transaction_dynamic(dynamic) do + dynamic([tx], ^dynamic or tx.value > ^0) + end + + def filter_contract_call_dynamic(dynamic) do + dynamic([tx, to_address: to_address], ^dynamic or not is_nil(to_address.contract_code)) + end + + def filter_token_transfer_dynamic(dynamic) do + # TokenTransfer.__struct__.__meta__.source + dynamic( + [tx], + ^dynamic or + fragment( + "NOT (SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NULL", + tx.hash + ) + ) + end + + def filter_token_creation_dynamic(dynamic) do + dynamic([tx, created_token: created_token], ^dynamic or (is_nil(tx.to_address_hash) and not is_nil(created_token))) + end + + @spec verified_contracts([ + paging_options | necessity_by_association_option | {:filter, :solidity | :vyper} | {:search, String.t()} + ]) :: [SmartContract.t()] + def verified_contracts(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + filter = Keyword.get(options, :filter, nil) + search_string = Keyword.get(options, :search, nil) + + query = from(contract in SmartContract, select: contract, order_by: [desc: :id]) + + query + |> filter_contracts(filter) + |> search_contracts(search_string) + |> handle_verified_contracts_paging_options(paging_options) + |> join_associations(necessity_by_association) + |> Repo.all() + end + + defp search_contracts(basic_query, nil), do: basic_query + + defp search_contracts(basic_query, search_string) do + from(contract in basic_query, + where: + ilike(contract.name, ^"%#{search_string}%") or + ilike(fragment("'0x' || encode(?, 'hex')", contract.address_hash), ^"%#{search_string}%") + ) + end + + defp filter_contracts(basic_query, :solidity) do + basic_query + |> where(is_vyper_contract: ^false) + end + + defp filter_contracts(basic_query, :vyper) do + basic_query + |> where(is_vyper_contract: ^true) + end + + defp filter_contracts(basic_query, _), do: basic_query + + def count_verified_contracts do + Repo.aggregate(SmartContract, :count, timeout: :infinity) + end + + def count_new_verified_contracts do + query = + from(contract in SmartContract, + select: contract.inserted_at, + where: fragment("NOW() - ? at time zone 'UTC' <= interval '24 hours'", contract.inserted_at) + ) + + query + |> Repo.aggregate(:count, timeout: :infinity) + end + + def count_contracts do + query = + from(address in Address, + select: address, + where: not is_nil(address.contract_code) + ) + + query + |> Repo.aggregate(:count, timeout: :infinity) + end + + def count_new_contracts do + query = + from(tx in Transaction, + select: tx, + where: + tx.status == ^:ok and + fragment("NOW() - ? at time zone 'UTC' <= interval '24 hours'", tx.created_contract_code_indexed_at) + ) + + query + |> Repo.aggregate(:count, timeout: :infinity) + end + + def count_verified_contracts_from_cache do + VerifiedContractsCounter.fetch() + end + + def count_new_verified_contracts_from_cache do + NewVerifiedContractsCounter.fetch() + end + + def count_contracts_from_cache do + ContractsCounter.fetch() + end + + def count_new_contracts_from_cache do + NewContractsCounter.fetch() + end + + def address_counters(address) do + validation_count_task = + Task.async(fn -> + address_to_validation_count(address.hash) + end) + + Task.start_link(fn -> + transaction_count(address) + end) + + Task.start_link(fn -> + token_transfers_count(address) + end) + + Task.start_link(fn -> + gas_usage_count(address) + end) + + [ + validation_count_task + ] + |> Task.yield_many(:infinity) + |> Enum.map(fn {_task, res} -> + case res do + {:ok, result} -> + result + + {:exit, reason} -> + raise "Query fetching address counters terminated: #{inspect(reason)}" + + nil -> + raise "Query fetching address counters timed out." + end + end) + |> List.to_tuple() + end + + def transaction_count(address) 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 + + def fetch_token_counters(address_hash, timeout) do + total_token_transfers_task = + Task.async(fn -> + TokenTransfersCounter.fetch(address_hash) + end) + + total_token_holders_task = + Task.async(fn -> + TokenHoldersCounter.fetch(address_hash) + end) + + [total_token_transfers_task, total_token_holders_task] + |> Task.yield_many(timeout) + |> Enum.map(fn {_task, res} -> + case res do + {:ok, result} -> + result + + {:exit, reason} -> + Logger.warn("Query fetching token counters terminated: #{inspect(reason)}") + 0 + + nil -> + Logger.warn("Query fetching token counters timed out.") + 0 + end + end) + |> List.to_tuple() + end end diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 704b1c9f48c9..2b60f9ffe91e 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -35,7 +35,7 @@ defmodule Explorer.Chain.Address do @type hash :: Hash.t() @typedoc """ - * `fetched_coin_balance` - The last fetched balance from Parity + * `fetched_coin_balance` - The last fetched balance from Nethermind * `fetched_coin_balance_block_number` - the `t:Explorer.Chain.Block.t/0` `t:Explorer.Chain.Block.block_number/0` for which `fetched_coin_balance` was fetched * `hash` - the hash of the address's public key @@ -259,14 +259,10 @@ defmodule Explorer.Chain.Address do ) end - @doc """ - Counts all the addresses. - """ - def count do - from( - a in Address, - select: fragment("COUNT(*)") - ) + def fetched_coin_balance(address_hash) when not is_nil(address_hash) do + Address + |> where([address], address.hash == ^address_hash) + |> select([address], address.fetched_coin_balance) end defimpl String.Chars do diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex index 03513580baf6..93174cb70c5f 100644 --- a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex @@ -23,7 +23,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do * `token_contract_address_hash` - The contract address hash foreign key. * `block_number` - The block's number that the transfer took place. * `value` - The value that's represents the balance. - * `token_id` - The token_id of the transferred token (applicable for ERC-1155 and ERC-721 tokens) + * `token_id` - The token_id of the transferred token (applicable for ERC-1155) * `token_type` - The type of the token """ @type t :: %__MODULE__{ @@ -165,7 +165,6 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do where: ctb.value > 0, left_join: t in Token, on: ctb.token_contract_address_hash == t.contract_address_hash, - preload: :token, select: {ctb, t}, order_by: [desc: ctb.value, asc: t.type, asc: t.name] ) diff --git a/apps/explorer/lib/explorer/chain/address/name.ex b/apps/explorer/lib/explorer/chain/address/name.ex index 926e15ef13e6..4364adf98795 100644 --- a/apps/explorer/lib/explorer/chain/address/name.ex +++ b/apps/explorer/lib/explorer/chain/address/name.ex @@ -24,7 +24,7 @@ defmodule Explorer.Chain.Address.Name do metadata: map() } - @primary_key false + @primary_key {:id, :integer, autogenerate: false} schema "address_names" do field(:name, :string) field(:primary, :boolean) diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex index cc0d579da1bd..1fd2219bfb90 100644 --- a/apps/explorer/lib/explorer/chain/address/token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex @@ -9,9 +9,6 @@ defmodule Explorer.Chain.Address.TokenBalance do use Explorer.Schema - import Ecto.Changeset - import Ecto.Query, only: [from: 2] - alias Explorer.Chain alias Explorer.Chain.Address.TokenBalance alias Explorer.Chain.{Address, Block, Hash, Token} @@ -89,8 +86,22 @@ defmodule Explorer.Chain.Address.TokenBalance do join: t in Token, on: tb.token_contract_address_hash == t.contract_address_hash, where: - ((tb.address_hash != ^@burn_address_hash and t.type != "ERC-721") or t.type == "ERC-20" or t.type == "ERC-1155") and + ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or t.type == "ERC-1155") and (is_nil(tb.value_fetched_at) or is_nil(tb.value)) ) end + + @doc """ + Builds an `Ecto.Query` to fetch the token balance of the given token contract hash of the given address in the given block. + """ + def fetch_token_balance(address_hash, token_contract_address_hash, block_number) do + from( + tb in TokenBalance, + where: tb.address_hash == ^address_hash, + where: tb.token_contract_address_hash == ^token_contract_address_hash, + where: tb.block_number <= ^block_number, + limit: ^1, + order_by: [desc: :block_number] + ) + end end diff --git a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_explorer.ex b/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex similarity index 89% rename from apps/explorer/lib/explorer/chain/address_internal_transaction_csv_explorer.ex rename to apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex index a2aca6a635a1..96350b398b4d 100644 --- a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_explorer.ex +++ b/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex @@ -16,10 +16,14 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do from_block = Chain.convert_date_to_min_block(from_period) to_block = Chain.convert_date_to_max_block(to_period) - address.hash - |> fetch_all_internal_transactions(from_block, to_block, @paging_options) - |> to_csv_format() - |> dump_to_stream() + res = + address.hash + |> fetch_all_internal_transactions(from_block, to_block, @paging_options) + |> Enum.sort_by(&{&1.block_number, &1.index, &1.transaction_index}, :desc) + |> to_csv_format() + |> dump_to_stream() + + res end defp fetch_all_internal_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do @@ -45,8 +49,11 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do end defp dump_to_stream(internal_transactions) do - internal_transactions - |> RFC4180.dump_to_stream() + res = + internal_transactions + |> RFC4180.dump_to_stream() + + res end defp to_csv_format(internal_transactions) do diff --git a/apps/explorer/lib/explorer/chain/address_log_csv_explorer.ex b/apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex similarity index 100% rename from apps/explorer/lib/explorer/chain/address_log_csv_explorer.ex rename to apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 008c9a0a8e4a..a68c4aabe08d 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -51,7 +51,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do |> Keyword.put(:to_block, to_block) transactions = Chain.address_to_transactions_without_rewards(address_hash, options) - new_acc = transactions ++ acc case Enum.split(transactions, @page_size) do diff --git a/apps/explorer/lib/explorer/chain/cache/contracts_counter.ex b/apps/explorer/lib/explorer/chain/cache/contracts_counter.ex new file mode 100644 index 000000000000..a5a7a61aa5ee --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/contracts_counter.ex @@ -0,0 +1,96 @@ +defmodule Explorer.Chain.Cache.ContractsCounter do + @moduledoc """ + Caches the number of contracts. + + It loads the count asynchronously and in a time interval of 30 minutes. + """ + + use GenServer + + alias Explorer.Chain + + @counter_type "contracts_counter" + + # It is undesirable to automatically start the consolidation in all environments. + # Consider the test environment: if the consolidation initiates but does not + # finish before a test ends, that test will fail. This way, hundreds of + # tests were failing before disabling the consolidation and the scheduler in + # the test env. + config = Application.compile_env(:explorer, Explorer.Chain.Cache.ContractsCounter) + @enable_consolidation Keyword.get(config, :enable_consolidation) + + @update_interval_in_seconds Keyword.get(config, :update_interval_in_seconds) + + @doc """ + Starts a process to periodically update the counter of all the contracts. + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, :timer.seconds(@update_interval_in_seconds)) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches the value for a `#{@counter_type}` counter type from the `last_fetched_counters` table. + """ + def fetch do + Chain.get_last_fetched_counter(@counter_type) + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + def consolidate do + all_counter = Chain.count_contracts() + + params = %{ + counter_type: @counter_type, + value: all_counter + } + + Chain.upsert_last_fetched_counter(params) + end + + @doc """ + Returns a boolean that indicates whether consolidation is enabled + + In order to choose whether or not to enable the scheduler and the initial + consolidation, change the following Explorer config: + + `config :explorer, Explorer.Chain.Cache.ContractsCounter, enable_consolidation: true` + + to: + + `config :explorer, Explorer.Chain.Cache.ContractsCounter, enable_consolidation: false` + """ + def enable_consolidation?, do: @enable_consolidation +end diff --git a/apps/explorer/lib/explorer/chain/cache/gas_usage.ex b/apps/explorer/lib/explorer/chain/cache/gas_usage.ex index 467a317631ae..79885a8a824a 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_usage.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_usage.ex @@ -11,7 +11,7 @@ defmodule Explorer.Chain.Cache.GasUsage do ] @default_cache_period :timer.hours(2) - config = Application.get_env(:explorer, __MODULE__) + config = Application.compile_env(:explorer, __MODULE__) @enabled Keyword.get(config, :enabled) use Explorer.Chain.MapCache, diff --git a/apps/explorer/lib/explorer/chain/cache/new_contracts_counter.ex b/apps/explorer/lib/explorer/chain/cache/new_contracts_counter.ex new file mode 100644 index 000000000000..4ff9163c44da --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/new_contracts_counter.ex @@ -0,0 +1,97 @@ +defmodule Explorer.Chain.Cache.NewContractsCounter do + @moduledoc """ + Caches the number of new contracts (created in last 24 hours). + + It loads the count asynchronously and in a time interval of 30 minutes. + """ + + use GenServer + + alias Explorer.Chain + + @counter_type "new_contracts_counter" + + # It is undesirable to automatically start the consolidation in all environments. + # Consider the test environment: if the consolidation initiates but does not + # finish before a test ends, that test will fail. This way, hundreds of + # tests were failing before disabling the consolidation and the scheduler in + # the test env. + config = Application.compile_env(:explorer, Explorer.Chain.Cache.NewContractsCounter) + @enable_consolidation Keyword.get(config, :enable_consolidation) + + @update_interval_in_seconds Keyword.get(config, :update_interval_in_seconds) + + @doc """ + Starts a process to periodically update the counter of new + contracts. (created in last 24 hours) + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, :timer.seconds(@update_interval_in_seconds)) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches the value for a `#{@counter_type}` counter type from the `last_fetched_counters` table. + """ + def fetch do + Chain.get_last_fetched_counter(@counter_type) + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + def consolidate do + new_all_counter = Chain.count_new_contracts() + + params = %{ + counter_type: @counter_type, + value: new_all_counter + } + + Chain.upsert_last_fetched_counter(params) + end + + @doc """ + Returns a boolean that indicates whether consolidation is enabled + + In order to choose whether or not to enable the scheduler and the initial + consolidation, change the following Explorer config: + + `config :explorer, Explorer.Chain.Cache.NewContractsCounter, enable_consolidation: true` + + to: + + `config :explorer, Explorer.Chain.Cache.NewContractsCounter, enable_consolidation: false` + """ + def enable_consolidation?, do: @enable_consolidation +end diff --git a/apps/explorer/lib/explorer/chain/cache/new_verified_contracts_counter.ex b/apps/explorer/lib/explorer/chain/cache/new_verified_contracts_counter.ex new file mode 100644 index 000000000000..afe13ac9b61f --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/new_verified_contracts_counter.ex @@ -0,0 +1,97 @@ +defmodule Explorer.Chain.Cache.NewVerifiedContractsCounter do + @moduledoc """ + Caches the number of new verfied contracts (verified in last 24 hours). + + It loads the count asynchronously and in a time interval of 30 minutes. + """ + + use GenServer + + alias Explorer.Chain + + @counter_type "new_verified_contracts_counter" + + # It is undesirable to automatically start the consolidation in all environments. + # Consider the test environment: if the consolidation initiates but does not + # finish before a test ends, that test will fail. This way, hundreds of + # tests were failing before disabling the consolidation and the scheduler in + # the test env. + config = Application.compile_env(:explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter) + @enable_consolidation Keyword.get(config, :enable_consolidation) + + @update_interval_in_seconds Keyword.get(config, :update_interval_in_seconds) + + @doc """ + Starts a process to periodically update the counter of new verified + contracts (verified in last 24 hours). + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, :timer.seconds(@update_interval_in_seconds)) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches the value for a `#{@counter_type}` counter type from the `last_fetched_counters` table. + """ + def fetch do + Chain.get_last_fetched_counter(@counter_type) + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + def consolidate do + new_verified_counter = Chain.count_new_verified_contracts() + + params = %{ + counter_type: @counter_type, + value: new_verified_counter + } + + Chain.upsert_last_fetched_counter(params) + end + + @doc """ + Returns a boolean that indicates whether consolidation is enabled + + In order to choose whether or not to enable the scheduler and the initial + consolidation, change the following Explorer config: + + `config :explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter, enable_consolidation: true` + + to: + + `config :explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter, enable_consolidation: false` + """ + def enable_consolidation?, do: @enable_consolidation +end diff --git a/apps/explorer/lib/explorer/chain/cache/token_exchange_rate.ex b/apps/explorer/lib/explorer/chain/cache/token_exchange_rate.ex index 36d75b86c185..73658af70029 100644 --- a/apps/explorer/lib/explorer/chain/cache/token_exchange_rate.ex +++ b/apps/explorer/lib/explorer/chain/cache/token_exchange_rate.ex @@ -10,7 +10,7 @@ defmodule Explorer.Chain.Cache.TokenExchangeRate do @cache_name :token_exchange_rate @last_update_key "last_update" - config = Application.get_env(:explorer, Explorer.Chain.Cache.TokenExchangeRate) + config = Application.compile_env(:explorer, Explorer.Chain.Cache.TokenExchangeRate) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/chain/cache/transaction.ex b/apps/explorer/lib/explorer/chain/cache/transaction.ex index a386127aa3e0..a5ab02482c43 100644 --- a/apps/explorer/lib/explorer/chain/cache/transaction.ex +++ b/apps/explorer/lib/explorer/chain/cache/transaction.ex @@ -28,13 +28,24 @@ defmodule Explorer.Chain.Cache.Transaction do def estimated_count do cached_value = __MODULE__.get_count() - if is_nil(cached_value) do + result_estimated_count = + if is_nil(cached_value) do + %Postgrex.Result{rows: [[rows]]} = + SQL.query!(Repo, "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='transactions'") + + rows + else + cached_value + end + + if result_estimated_count < 100_000 do + # use no estimate count when the tx is less %Postgrex.Result{rows: [[rows]]} = - SQL.query!(Repo, "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='transactions'") + SQL.query!(Repo, "SELECT count(*) AS estimate FROM public.transactions WHERE block_number>0") rows else - cached_value + result_estimated_count end end diff --git a/apps/explorer/lib/explorer/chain/cache/transactions_api_v2.ex b/apps/explorer/lib/explorer/chain/cache/transactions_api_v2.ex new file mode 100644 index 000000000000..0a5cf715c1bc --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/transactions_api_v2.ex @@ -0,0 +1,27 @@ +defmodule Explorer.Chain.Cache.TransactionsApiV2 do + @moduledoc """ + Caches the latest imported transactions + """ + + alias Explorer.Chain.Transaction + + use Explorer.Chain.OrderedCache, + name: :transactions_api_v2, + max_size: 51, + preloads: [ + :block, + created_contract_address: :names, + from_address: :names, + to_address: :names + ], + ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval], + global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl] + + @type element :: Transaction.t() + + @type id :: {non_neg_integer(), non_neg_integer()} + + def element_to_id(%Transaction{block_number: block_number, index: index}) do + {block_number, index} + end +end diff --git a/apps/explorer/lib/explorer/chain/cache/verified_contracts_counter.ex b/apps/explorer/lib/explorer/chain/cache/verified_contracts_counter.ex new file mode 100644 index 000000000000..da504358d164 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/verified_contracts_counter.ex @@ -0,0 +1,96 @@ +defmodule Explorer.Chain.Cache.VerifiedContractsCounter do + @moduledoc """ + Caches the number of verified contracts. + + It loads the count asynchronously and in a time interval of 30 minutes. + """ + + use GenServer + + alias Explorer.Chain + + @counter_type "verified_contracts_counter" + + # It is undesirable to automatically start the consolidation in all environments. + # Consider the test environment: if the consolidation initiates but does not + # finish before a test ends, that test will fail. This way, hundreds of + # tests were failing before disabling the consolidation and the scheduler in + # the test env. + config = Application.compile_env(:explorer, Explorer.Chain.Cache.VerifiedContractsCounter) + @enable_consolidation Keyword.get(config, :enable_consolidation) + + @update_interval_in_seconds Keyword.get(config, :update_interval_in_seconds) + + @doc """ + Starts a process to periodically update the counter of verified contracts. + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, :timer.seconds(@update_interval_in_seconds)) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches the value for a `#{@counter_type}` counter type from the `last_fetched_counters` table. + """ + def fetch do + Chain.get_last_fetched_counter(@counter_type) + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + def consolidate do + verified_counter = Chain.count_verified_contracts() + + params = %{ + counter_type: @counter_type, + value: verified_counter + } + + Chain.upsert_last_fetched_counter(params) + end + + @doc """ + Returns a boolean that indicates whether consolidation is enabled + + In order to choose whether or not to enable the scheduler and the initial + consolidation, change the following Explorer config: + + `config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, enable_consolidation: true` + + to: + + `config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, enable_consolidation: false` + """ + def enable_consolidation?, do: @enable_consolidation +end diff --git a/apps/explorer/lib/explorer/chain/data.ex b/apps/explorer/lib/explorer/chain/data.ex index ed07de124998..09f526871799 100644 --- a/apps/explorer/lib/explorer/chain/data.ex +++ b/apps/explorer/lib/explorer/chain/data.ex @@ -68,6 +68,8 @@ defmodule Explorer.Chain.Data do |> IO.iodata_to_binary() end + def to_string(nil), do: "0x" + @doc """ Casts `term` to `t:t/0`. diff --git a/apps/explorer/lib/explorer/chain/events/db_sender.ex b/apps/explorer/lib/explorer/chain/events/db_sender.ex index 2891e8bde281..186beebec13b 100644 --- a/apps/explorer/lib/explorer/chain/events/db_sender.ex +++ b/apps/explorer/lib/explorer/chain/events/db_sender.ex @@ -3,6 +3,7 @@ defmodule Explorer.Chain.Events.DBSender do Sends events to Postgres. """ alias Explorer.Repo + alias Explorer.Utility.EventNotification def send_data(event_type) do payload = encode_payload({:chain_event, event_type}) @@ -11,7 +12,10 @@ defmodule Explorer.Chain.Events.DBSender do def send_data(event_type, broadcast_type, event_data) do payload = encode_payload({:chain_event, event_type, broadcast_type, event_data}) - send_notify(payload) + + with {:ok, %{id: event_notification_id}} <- save_event_notification(payload) do + send_notify(to_string(event_notification_id)) + end end defp encode_payload(payload) do @@ -23,4 +27,10 @@ defmodule Explorer.Chain.Events.DBSender do defp send_notify(payload) do Repo.query!("select pg_notify('chain_event', $1::text);", [payload]) end + + defp save_event_notification(event_data) do + event_data + |> EventNotification.new_changeset() + |> Repo.insert() + end end diff --git a/apps/explorer/lib/explorer/chain/events/listener.ex b/apps/explorer/lib/explorer/chain/events/listener.ex index 2d93eeada0b4..c03e1746ef43 100644 --- a/apps/explorer/lib/explorer/chain/events/listener.ex +++ b/apps/explorer/lib/explorer/chain/events/listener.ex @@ -5,6 +5,8 @@ defmodule Explorer.Chain.Events.Listener do use GenServer + alias Explorer.Repo + alias Explorer.Utility.EventNotification alias Postgrex.Notifications def start_link(_) do @@ -24,12 +26,20 @@ defmodule Explorer.Chain.Events.Listener do def handle_info({:notification, _pid, _ref, _topic, payload}, state) do payload + |> expand_payload() |> decode_payload!() |> broadcast() {:noreply, state} end + defp expand_payload(payload) do + case Integer.parse(payload) do + {event_notification_id, ""} -> fetch_and_delete_event_notification(event_notification_id) + _ -> payload + end + end + # sobelow_skip ["Misc.BinToTerm"] defp decode_payload!(payload) do payload @@ -52,4 +62,15 @@ defmodule Explorer.Chain.Events.Listener do end end) end + + defp fetch_and_delete_event_notification(id) do + case Repo.get(EventNotification, id) do + nil -> + nil + + %{data: data} = notification -> + Repo.delete(notification) + data + end + end end diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index b3aa19ac423f..8640d47e3a05 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -4,10 +4,13 @@ defmodule Explorer.Chain.Import do """ alias Ecto.Changeset + alias Explorer.Account.Notify alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Import alias Explorer.Repo + require Logger + @stages [ Import.Stage.Addresses, Import.Stage.AddressReferencing, @@ -126,6 +129,7 @@ defmodule Explorer.Chain.Import do {:ok, valid_runner_option_pairs} <- validate_runner_options_pairs(runner_options_pairs), {:ok, runner_to_changes_list} <- runner_to_changes_list(valid_runner_option_pairs), {:ok, data} <- insert_runner_to_changes_list(runner_to_changes_list, options) do + Notify.async(data[:transactions]) Publisher.broadcast(data, Map.get(options, :broadcast, false)) {:ok, data} end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex index f2967ec06a63..62b0cfa9b2e5 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex @@ -10,6 +10,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalances do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Address.CoinBalance alias Explorer.Chain.{Block, Hash, Import, Wei} + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -44,7 +45,12 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalances do |> Map.put(:timestamps, timestamps) Multi.run(multi, :address_coin_balances, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :address_referencing, + :coin_balances, + :address_coin_balances + ) end) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex index 7d1a091984b0..9b7aa93e9ba5 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex @@ -10,6 +10,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Address.CoinBalanceDaily alias Explorer.Chain.{Hash, Import, Wei} + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -44,7 +45,12 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do |> Map.put(:timestamps, timestamps) Multi.run(multi, :address_coin_balances_daily, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :address_referencing, + :coin_balances_daily, + :address_coin_balances_daily + ) end) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex index c33d7cf24d37..4480cd883b01 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex @@ -11,6 +11,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.{Hash, Import} alias Explorer.Chain.Import.Runner.Tokens + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -107,8 +108,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do |> Map.put(:timestamps, timestamps) # Enforce ShareLocks tables order (see docs: sharelocks.md) - multi - |> Multi.run(:acquire_contract_address_tokens, fn repo, _ -> + run_func = fn repo -> token_contract_address_hashes_and_ids = changes_list |> Enum.map(fn change -> @@ -119,22 +119,44 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do |> Enum.uniq() Tokens.acquire_contract_address_tokens(repo, token_contract_address_hashes_and_ids) + end + + multi + |> Multi.run(:acquire_contract_address_tokens, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> run_func.(repo) end, + :block_following, + :current_token_balances, + :acquire_contract_address_tokens + ) end) |> Multi.run(:address_current_token_balances, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_following, + :current_token_balances, + :acquire_contract_address_tokens + ) end) |> Multi.run(:address_current_token_balances_update_token_holder_counts, fn repo, %{ address_current_token_balances: upserted_balances } -> - token_holder_count_deltas = upserted_balances_to_holder_count_deltas(upserted_balances) - - # ShareLocks order already enforced by `acquire_contract_address_tokens` (see docs: sharelocks.md) - Tokens.update_holder_counts_with_deltas( - repo, - token_holder_count_deltas, - insert_options + Instrumenter.block_import_stage_runner( + fn -> + token_holder_count_deltas = upserted_balances_to_holder_count_deltas(upserted_balances) + + # ShareLocks order already enforced by `acquire_contract_address_tokens` (see docs: sharelocks.md) + Tokens.update_holder_counts_with_deltas( + repo, + token_holder_count_deltas, + insert_options + ) + end, + :block_following, + :current_token_balances, + :acquire_contract_address_tokens ) end) end @@ -220,51 +242,15 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce CurrentTokenBalance ShareLocks order (see docs: sharelocks.md) - %{ - changes_list_no_token_id: changes_list_no_token_id, - changes_list_with_token_id: changes_list_with_token_id - } = + ordered_changes_list = changes_list - |> Enum.reduce(%{changes_list_no_token_id: [], changes_list_with_token_id: []}, fn change, acc -> - updated_change = - if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do - change - else - Map.put(change, :token_id, nil) - end - - if updated_change.token_id do - changes_list_with_token_id = [updated_change | acc.changes_list_with_token_id] - - %{ - changes_list_no_token_id: acc.changes_list_no_token_id, - changes_list_with_token_id: changes_list_with_token_id - } + |> Enum.map(fn change -> + if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do + change else - changes_list_no_token_id = [updated_change | acc.changes_list_no_token_id] - - %{ - changes_list_no_token_id: changes_list_no_token_id, - changes_list_with_token_id: acc.changes_list_with_token_id - } + Map.put(change, :token_id, nil) end end) - - ordered_changes_list_no_token_id = - changes_list_no_token_id - |> Enum.group_by(fn %{ - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash - } -> - {address_hash, token_contract_address_hash} - end) - |> Enum.map(fn {_, grouped_address_token_balances} -> - Enum.max_by(grouped_address_token_balances, fn %{block_number: block_number} -> block_number end) - end) - |> Enum.sort_by(&{&1.token_contract_address_hash, &1.address_hash}) - - ordered_changes_list_with_token_id = - changes_list_with_token_id |> Enum.group_by(fn %{ address_hash: address_hash, token_contract_address_hash: token_contract_address_hash, @@ -273,33 +259,18 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do {address_hash, token_contract_address_hash, token_id} end) |> Enum.map(fn {_, grouped_address_token_balances} -> - Enum.max_by(grouped_address_token_balances, fn %{block_number: block_number} -> block_number end) + Enum.max_by(grouped_address_token_balances, fn balance -> + {Map.get(balance, :block_number), Map.get(balance, :value_fetched_at)} + end) end) |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id, &1.address_hash}) - {:ok, inserted_changes_list_no_token_id} = - if Enum.count(ordered_changes_list_no_token_id) > 0 do - Import.insert_changes_list( - repo, - ordered_changes_list_no_token_id, - conflict_target: {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash) WHERE token_id IS NULL>}, - on_conflict: on_conflict, - for: CurrentTokenBalance, - returning: true, - timeout: timeout, - timestamps: timestamps - ) - else - {:ok, []} - end - - {:ok, inserted_changes_list_with_token_id} = - if Enum.count(ordered_changes_list_with_token_id) > 0 do + {:ok, inserted_changes_list} = + if Enum.count(ordered_changes_list) > 0 do Import.insert_changes_list( repo, - ordered_changes_list_with_token_id, - conflict_target: - {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, token_id) WHERE token_id IS NOT NULL>}, + ordered_changes_list, + conflict_target: {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, COALESCE(token_id, -1))>}, on_conflict: on_conflict, for: CurrentTokenBalance, returning: true, @@ -310,7 +281,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do {:ok, []} end - inserted_changes_list_no_token_id ++ inserted_changes_list_with_token_id + inserted_changes_list end defp default_on_conflict do @@ -329,9 +300,10 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do ], where: fragment("? < EXCLUDED.block_number", current_token_balance.block_number) or - (fragment("EXCLUDED.value IS NOT NULL") and - is_nil(current_token_balance.value_fetched_at) and - fragment("? = EXCLUDED.block_number", current_token_balance.block_number)) + (fragment("? = EXCLUDED.block_number", current_token_balance.block_number) and + fragment("EXCLUDED.value IS NOT NULL") and + (is_nil(current_token_balance.value_fetched_at) or + fragment("? < EXCLUDED.value_fetched_at", current_token_balance.value_fetched_at))) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex index d80f561bbe7a..544e388074c5 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex @@ -10,6 +10,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Address.TokenBalance alias Explorer.Chain.Import + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -42,7 +43,12 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do |> Map.put(:timestamps, timestamps) Multi.run(multi, :address_token_balances, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :token_blances, + :address_token_balances + ) end) end @@ -60,58 +66,15 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce TokenBalance ShareLocks order (see docs: sharelocks.md) - %{ - changes_list_no_token_id: changes_list_no_token_id, - changes_list_with_token_id: changes_list_with_token_id - } = + ordered_changes_list = changes_list - |> Enum.reduce(%{changes_list_no_token_id: [], changes_list_with_token_id: []}, fn change, acc -> - updated_change = - if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do - change - else - Map.put(change, :token_id, nil) - end - - if updated_change.token_id do - changes_list_with_token_id = [updated_change | acc.changes_list_with_token_id] - - %{ - changes_list_no_token_id: acc.changes_list_no_token_id, - changes_list_with_token_id: changes_list_with_token_id - } + |> Enum.map(fn change -> + if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do + change else - changes_list_no_token_id = [updated_change | acc.changes_list_no_token_id] - - %{ - changes_list_no_token_id: changes_list_no_token_id, - changes_list_with_token_id: acc.changes_list_with_token_id - } + Map.put(change, :token_id, nil) end end) - - ordered_changes_list_no_token_id = - changes_list_no_token_id - |> Enum.group_by(fn %{ - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number - } -> - {token_contract_address_hash, address_hash, block_number} - end) - |> Enum.map(fn {_, grouped_address_token_balances} -> - dedup = Enum.dedup(grouped_address_token_balances) - - if Enum.count(dedup) > 1 do - Enum.max_by(dedup, fn %{value_fetched_at: value_fetched_at} -> value_fetched_at end) - else - Enum.at(dedup, 0) - end - end) - |> Enum.sort_by(&{&1.token_contract_address_hash, &1.address_hash, &1.block_number}) - - ordered_changes_list_with_token_id = - changes_list_with_token_id |> Enum.group_by(fn %{ address_hash: address_hash, token_contract_address_hash: token_contract_address_hash, @@ -122,20 +85,20 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do end) |> Enum.map(fn {_, grouped_address_token_balances} -> if Enum.count(grouped_address_token_balances) > 1 do - Enum.max_by(grouped_address_token_balances, fn %{value_fetched_at: value_fetched_at} -> value_fetched_at end) + Enum.max_by(grouped_address_token_balances, fn balance -> Map.get(balance, :value_fetched_at) end) else Enum.at(grouped_address_token_balances, 0) end end) |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id, &1.address_hash, &1.block_number}) - {:ok, inserted_changes_list_no_token_id} = - if Enum.count(ordered_changes_list_no_token_id) > 0 do + {:ok, inserted_changes_list} = + if Enum.count(ordered_changes_list) > 0 do Import.insert_changes_list( repo, - ordered_changes_list_no_token_id, + ordered_changes_list, conflict_target: - {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, block_number) WHERE token_id IS NULL>}, + {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, COALESCE(token_id, -1), block_number)>}, on_conflict: on_conflict, for: TokenBalance, returning: true, @@ -146,26 +109,6 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do {:ok, []} end - {:ok, inserted_changes_list_with_token_id} = - if Enum.count(ordered_changes_list_with_token_id) > 0 do - Import.insert_changes_list( - repo, - ordered_changes_list_with_token_id, - conflict_target: - {:unsafe_fragment, - ~s<(address_hash, token_contract_address_hash, token_id, block_number) WHERE token_id IS NOT NULL>}, - on_conflict: on_conflict, - for: TokenBalance, - returning: true, - timeout: timeout, - timestamps: timestamps - ) - else - {:ok, []} - end - - inserted_changes_list = inserted_changes_list_no_token_id ++ inserted_changes_list_with_token_id - {:ok, inserted_changes_list} end @@ -174,7 +117,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do token_balance in TokenBalance, update: [ set: [ - value: fragment("EXCLUDED.value"), + value: fragment("COALESCE(EXCLUDED.value, ?)", token_balance.value), value_fetched_at: fragment("EXCLUDED.value_fetched_at"), token_type: fragment("EXCLUDED.token_type"), inserted_at: fragment("LEAST(EXCLUDED.inserted_at, ?)", token_balance.inserted_at), @@ -182,9 +125,8 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do ] ], where: - fragment("EXCLUDED.value IS NOT NULL") and - (is_nil(token_balance.value_fetched_at) or - fragment("? < EXCLUDED.value_fetched_at", token_balance.value_fetched_at)) + is_nil(token_balance.value_fetched_at) or fragment("EXCLUDED.value_fetched_at IS NULL") or + fragment("? < EXCLUDED.value_fetched_at", token_balance.value_fetched_at) ) end end diff --git a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex index c62ae6d1236f..2b703a0100fd 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex @@ -8,6 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Addresses do alias Ecto.{Multi, Repo} alias Explorer.Chain.{Address, Hash, Import, Transaction} alias Explorer.Chain.Import.Runner + alias Explorer.Prometheus.Instrumenter import Ecto.Query, only: [from: 2] @@ -59,11 +60,21 @@ defmodule Explorer.Chain.Import.Runner.Addresses do multi |> Multi.run(:addresses, fn repo, _ -> - insert(repo, changes_list_with_defaults, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list_with_defaults, insert_options) end, + :addresses, + :addresses, + :addresses + ) end) |> Multi.run(:created_address_code_indexed_at_transactions, fn repo, %{addresses: addresses} when is_list(addresses) -> - update_transactions(repo, addresses, update_transactions_options) + Instrumenter.block_import_stage_runner( + fn -> update_transactions(repo, addresses, update_transactions_options) end, + :addresses, + :addresses, + :created_address_code_indexed_at_transactions + ) end) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex b/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex index fd656d35edf6..cf13ae1b2278 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex @@ -8,6 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Block.Rewards do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Block.Reward alias Explorer.Chain.Import + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -37,7 +38,14 @@ defmodule Explorer.Chain.Import.Runner.Block.Rewards do |> Map.put_new(:timeout, @timeout) |> Map.put(:timestamps, timestamps) - Multi.run(multi, option_key(), fn repo, _ -> insert(repo, changes_list, insert_options) end) + Multi.run(multi, option_key(), fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_following, + :rewards, + option_key() + ) + end) end @impl Import.Runner diff --git a/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex b/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex index 18b8eb089c38..fd752c67bee4 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex @@ -9,6 +9,7 @@ defmodule Explorer.Chain.Import.Runner.Block.SecondDegreeRelations do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.{Block, Hash, Import} + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -46,7 +47,12 @@ defmodule Explorer.Chain.Import.Runner.Block.SecondDegreeRelations do |> Map.put_new(:timeout, @timeout) Multi.run(multi, :block_second_degree_relations, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_following, + :second_degree_relations, + :block_second_degree_relations + ) end) end @@ -57,7 +63,7 @@ defmodule Explorer.Chain.Import.Runner.Block.SecondDegreeRelations do optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout }) :: - {:ok, %{nephew_hash: Hash.Full.t(), uncle_hash: Hash.Full.t(), index: non_neg_integer()}} + {:ok, nil | %{nephew_hash: Hash.Full.t(), uncle_hash: Hash.Full.t(), index: non_neg_integer()}} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout} = options) when is_atom(repo) and is_list(changes_list) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 98e88de110b2..1f300ae27513 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -13,6 +13,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner.Address.CurrentTokenBalances alias Explorer.Chain.Import.Runner.Tokens + alias Explorer.Prometheus.Instrumenter alias Explorer.Repo, as: ExplorerRepo @behaviour Runner @@ -61,8 +62,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do consensus_block_numbers = consensus_block_numbers(changes_list) # Enforce ShareLocks tables order (see docs: sharelocks.md) - multi - |> Multi.run(:lose_consensus, fn repo, _ -> + run_func = fn repo -> {:ok, nonconsensus_items} = lose_consensus(repo, hashes, consensus_block_numbers, changes_list, insert_options) nonconsensus_hashes = @@ -75,64 +75,141 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end {:ok, nonconsensus_hashes} + end + + multi + |> Multi.run(:lose_consensus, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> run_func.(repo) end, + :address_referencing, + :blocks, + :lose_consensus + ) end) |> Multi.run(:blocks, fn repo, _ -> - # Note, needs to be executed after `lose_consensus` for lock acquisition - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> + # Note, needs to be executed after `lose_consensus` for lock acquisition + insert(repo, changes_list, insert_options) + end, + :address_referencing, + :blocks, + :blocks + ) end) |> Multi.run(:new_pending_operations, fn repo, %{lose_consensus: nonconsensus_hashes} -> - new_pending_operations(repo, nonconsensus_hashes, hashes_for_pending_block_operations, insert_options) + Instrumenter.block_import_stage_runner( + fn -> + new_pending_operations(repo, nonconsensus_hashes, hashes_for_pending_block_operations, insert_options) + end, + :address_referencing, + :blocks, + :new_pending_operations + ) end) |> Multi.run(:uncle_fetched_block_second_degree_relations, fn repo, _ -> - update_block_second_degree_relations(repo, hashes, %{ - timeout: - options[Runner.Block.SecondDegreeRelations.option_key()][:timeout] || - Runner.Block.SecondDegreeRelations.timeout(), - timestamps: timestamps - }) + Instrumenter.block_import_stage_runner( + fn -> + update_block_second_degree_relations(repo, hashes, %{ + timeout: + options[Runner.Block.SecondDegreeRelations.option_key()][:timeout] || + Runner.Block.SecondDegreeRelations.timeout(), + timestamps: timestamps + }) + end, + :address_referencing, + :blocks, + :uncle_fetched_block_second_degree_relations + ) end) |> Multi.run(:delete_rewards, fn repo, _ -> - delete_rewards(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> delete_rewards(repo, changes_list, insert_options) end, + :address_referencing, + :blocks, + :delete_rewards + ) end) |> Multi.run(:fork_transactions, fn repo, _ -> - fork_transactions(%{ - repo: repo, - timeout: options[Runner.Transactions.option_key()][:timeout] || Runner.Transactions.timeout(), - timestamps: timestamps, - blocks_changes: changes_list - }) + Instrumenter.block_import_stage_runner( + fn -> + fork_transactions(%{ + repo: repo, + timeout: options[Runner.Transactions.option_key()][:timeout] || Runner.Transactions.timeout(), + timestamps: timestamps, + blocks_changes: changes_list + }) + end, + :address_referencing, + :blocks, + :fork_transactions + ) end) |> Multi.run(:derive_transaction_forks, fn repo, %{fork_transactions: transactions} -> - derive_transaction_forks(%{ - repo: repo, - timeout: options[Runner.Transaction.Forks.option_key()][:timeout] || Runner.Transaction.Forks.timeout(), - timestamps: timestamps, - transactions: transactions - }) + Instrumenter.block_import_stage_runner( + fn -> + derive_transaction_forks(%{ + repo: repo, + timeout: options[Runner.Transaction.Forks.option_key()][:timeout] || Runner.Transaction.Forks.timeout(), + timestamps: timestamps, + transactions: transactions + }) + end, + :address_referencing, + :blocks, + :derive_transaction_forks + ) end) |> Multi.run(:acquire_contract_address_tokens, fn repo, _ -> - acquire_contract_address_tokens(repo, consensus_block_numbers) + Instrumenter.block_import_stage_runner( + fn -> acquire_contract_address_tokens(repo, consensus_block_numbers) end, + :address_referencing, + :blocks, + :acquire_contract_address_tokens + ) end) |> Multi.run(:delete_address_token_balances, fn repo, _ -> - delete_address_token_balances(repo, consensus_block_numbers, insert_options) + Instrumenter.block_import_stage_runner( + fn -> delete_address_token_balances(repo, consensus_block_numbers, insert_options) end, + :address_referencing, + :blocks, + :delete_address_token_balances + ) end) |> Multi.run(:delete_address_current_token_balances, fn repo, _ -> - delete_address_current_token_balances(repo, consensus_block_numbers, insert_options) + Instrumenter.block_import_stage_runner( + fn -> delete_address_current_token_balances(repo, consensus_block_numbers, insert_options) end, + :address_referencing, + :blocks, + :delete_address_current_token_balances + ) end) |> Multi.run(:derive_address_current_token_balances, fn repo, %{ delete_address_current_token_balances: deleted_address_current_token_balances } -> - derive_address_current_token_balances(repo, deleted_address_current_token_balances, insert_options) + Instrumenter.block_import_stage_runner( + fn -> derive_address_current_token_balances(repo, deleted_address_current_token_balances, insert_options) end, + :address_referencing, + :blocks, + :derive_address_current_token_balances + ) end) |> Multi.run(:blocks_update_token_holder_counts, fn repo, %{ delete_address_current_token_balances: deleted, derive_address_current_token_balances: inserted } -> - deltas = CurrentTokenBalances.token_holder_count_deltas(%{deleted: deleted, inserted: inserted}) - Tokens.update_holder_counts_with_deltas(repo, deltas, insert_options) + Instrumenter.block_import_stage_runner( + fn -> + deltas = CurrentTokenBalances.token_holder_count_deltas(%{deleted: deleted, inserted: inserted}) + Tokens.update_holder_counts_with_deltas(repo, deltas, insert_options) + end, + :address_referencing, + :blocks, + :blocks_update_token_holder_counts + ) end) end @@ -359,14 +436,14 @@ defmodule Explorer.Chain.Import.Runner.Blocks do |> MapSet.union(MapSet.new(hashes)) |> Enum.sort() |> Enum.map(fn hash -> - %{block_hash: hash, fetch_internal_transactions: true} + %{block_hash: hash} end) Import.insert_changes_list( repo, sorted_pending_ops, conflict_target: :block_hash, - on_conflict: PendingBlockOperation.default_on_conflict(), + on_conflict: :nothing, for: PendingBlockOperation, returning: true, timeout: timeout, diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 55189bacb168..557355e836e6 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -10,6 +10,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction} alias Explorer.Chain.Import.Runner + alias Explorer.Prometheus.Instrumenter alias Explorer.Repo, as: ExplorerRepo import Ecto.Query, only: [from: 2, or_where: 3] @@ -54,26 +55,53 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # Enforce ShareLocks tables order (see docs: sharelocks.md) multi |> Multi.run(:acquire_blocks, fn repo, _ -> - acquire_blocks(repo, changes_list) + Instrumenter.block_import_stage_runner( + fn -> acquire_blocks(repo, changes_list) end, + :block_pending, + :internal_transactions, + :acquire_blocks + ) end) |> Multi.run(:acquire_pending_internal_txs, fn repo, %{acquire_blocks: block_hashes} -> - acquire_pending_internal_txs(repo, block_hashes) + Instrumenter.block_import_stage_runner( + fn -> acquire_pending_internal_txs(repo, block_hashes) end, + :block_pending, + :internal_transactions, + :acquire_pending_internal_txs + ) end) |> Multi.run(:acquire_transactions, fn repo, %{acquire_pending_internal_txs: pending_block_hashes} -> - acquire_transactions(repo, pending_block_hashes) + Instrumenter.block_import_stage_runner( + fn -> acquire_transactions(repo, pending_block_hashes) end, + :block_pending, + :internal_transactions, + :acquire_transactions + ) end) |> Multi.run(:invalid_block_numbers, fn _, %{acquire_transactions: transactions} -> - invalid_block_numbers(transactions, internal_transactions_params) + Instrumenter.block_import_stage_runner( + fn -> invalid_block_numbers(transactions, internal_transactions_params) end, + :block_pending, + :internal_transactions, + :invalid_block_numbers + ) end) |> Multi.run(:valid_internal_transactions, fn _, %{ acquire_transactions: transactions, invalid_block_numbers: invalid_block_numbers } -> - valid_internal_transactions( - transactions, - internal_transactions_params, - invalid_block_numbers + Instrumenter.block_import_stage_runner( + fn -> + valid_internal_transactions( + transactions, + internal_transactions_params, + invalid_block_numbers + ) + end, + :block_pending, + :internal_transactions, + :valid_internal_transactions ) end) |> Multi.run(:valid_internal_transactions_without_first_traces_of_trivial_transactions, fn _, @@ -81,35 +109,69 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do valid_internal_transactions: valid_internal_transactions } -> - valid_internal_transactions_without_first_trace(valid_internal_transactions) + Instrumenter.block_import_stage_runner( + fn -> valid_internal_transactions_without_first_trace(valid_internal_transactions) end, + :block_pending, + :internal_transactions, + :valid_internal_transactions_without_first_traces_of_trivial_transactions + ) end) |> Multi.run(:remove_left_over_internal_transactions, fn repo, - %{valid_internal_transactions: valid_internal_transactions} -> - remove_left_over_internal_transactions(repo, valid_internal_transactions) + %{ + valid_internal_transactions: valid_internal_transactions + } -> + Instrumenter.block_import_stage_runner( + fn -> remove_left_over_internal_transactions(repo, valid_internal_transactions) end, + :block_pending, + :internal_transactions, + :remove_left_over_internal_transactions + ) end) |> Multi.run(:internal_transactions, fn repo, %{ valid_internal_transactions_without_first_traces_of_trivial_transactions: valid_internal_transactions_without_first_traces_of_trivial_transactions } -> - insert(repo, valid_internal_transactions_without_first_traces_of_trivial_transactions, insert_options) + Instrumenter.block_import_stage_runner( + fn -> + insert(repo, valid_internal_transactions_without_first_traces_of_trivial_transactions, insert_options) + end, + :block_pending, + :internal_transactions, + :internal_transactions + ) end) |> Multi.run(:update_transactions, fn repo, %{ valid_internal_transactions: valid_internal_transactions, acquire_transactions: transactions } -> - update_transactions(repo, valid_internal_transactions, transactions, update_transactions_options) + Instrumenter.block_import_stage_runner( + fn -> update_transactions(repo, valid_internal_transactions, transactions, update_transactions_options) end, + :block_pending, + :internal_transactions, + :update_transactions + ) end) |> Multi.run(:remove_consensus_of_invalid_blocks, fn repo, %{invalid_block_numbers: invalid_block_numbers} -> - remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) + Instrumenter.block_import_stage_runner( + fn -> remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) end, + :block_pending, + :internal_transactions, + :remove_consensus_of_invalid_blocks + ) end) |> Multi.run(:update_pending_blocks_status, fn repo, %{ acquire_pending_internal_txs: pending_block_hashes, remove_consensus_of_invalid_blocks: invalid_block_hashes } -> - update_pending_blocks_status(repo, pending_block_hashes, invalid_block_hashes) + Instrumenter.block_import_stage_runner( + fn -> update_pending_blocks_status(repo, pending_block_hashes, invalid_block_hashes) end, + :block_pending, + :internal_transactions, + :update_pending_blocks_status + ) end) end @@ -241,7 +303,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do from( pending_ops in PendingBlockOperation, where: pending_ops.block_hash in ^block_hashes, - where: pending_ops.fetch_internal_transactions, select: pending_ops.block_hash, # Enforce PendingBlockOperation ShareLocks order (see docs: sharelocks.md) order_by: [asc: pending_ops.block_hash], @@ -333,8 +394,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) variant = Keyword.fetch!(json_rpc_named_arguments, :variant) - # we exclude first traces from storing in the DB only in case of Parity variant (Parity/Nethermind). Todo: implement the same for Geth - if variant == EthereumJSONRPC.Parity do + # we exclude first traces from storing in the DB only in case of Nethermind variant (Nethermind/OpenEthereum). Todo: implement the same for Geth + if variant == EthereumJSONRPC.Nethermind do valid_internal_transactions_without_first_trace = valid_internal_transactions |> Enum.reject(fn trace -> diff --git a/apps/explorer/lib/explorer/chain/import/runner/logs.ex b/apps/explorer/lib/explorer/chain/import/runner/logs.ex index 1a816c0a932a..c90adaa04a41 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/logs.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/logs.ex @@ -7,6 +7,7 @@ defmodule Explorer.Chain.Import.Runner.Logs do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.{Import, Log} + alias Explorer.Prometheus.Instrumenter import Ecto.Query, only: [from: 2] @@ -41,7 +42,12 @@ defmodule Explorer.Chain.Import.Runner.Logs do |> Map.put(:timestamps, timestamps) Multi.run(multi, :logs, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :logs, + :logs + ) end) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex index 3935fb53b51f..f50e98691e61 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex @@ -9,6 +9,7 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.{Import, TokenTransfer} + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -41,7 +42,12 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do |> Map.put(:timestamps, timestamps) Multi.run(multi, :token_transfers, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :token_transfers, + :token_transfers + ) end) end @@ -57,7 +63,7 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do # Enforce TokenTransfer ShareLocks order (see docs: sharelocks.md) ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.block_hash, &1.log_index}) - {:ok, _} = + {:ok, inserted} = Import.insert_changes_list( repo, ordered_changes_list, @@ -68,6 +74,8 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do timeout: timeout, timestamps: timestamps ) + + {:ok, inserted} end defp default_on_conflict do @@ -81,19 +89,19 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do from_address_hash: fragment("EXCLUDED.from_address_hash"), to_address_hash: fragment("EXCLUDED.to_address_hash"), token_contract_address_hash: fragment("EXCLUDED.token_contract_address_hash"), - token_id: fragment("EXCLUDED.token_id"), + token_ids: fragment("EXCLUDED.token_ids"), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_transfer.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_transfer.updated_at) ] ], where: fragment( - "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_id) IS DISTINCT FROM (?, ? ,? , ?, ?)", + "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_ids) IS DISTINCT FROM (?, ? ,? , ?, ?)", token_transfer.amount, token_transfer.from_address_hash, token_transfer.to_address_hash, token_transfer.token_contract_address_hash, - token_transfer.token_id + token_transfer.token_ids ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex index 4d38921854d5..84d0155320d9 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex @@ -9,6 +9,7 @@ defmodule Explorer.Chain.Import.Runner.Tokens do alias Ecto.{Multi, Repo} alias Explorer.Chain.{Hash, Import, Token} + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -155,7 +156,12 @@ defmodule Explorer.Chain.Import.Runner.Tokens do |> Map.put(:timestamps, timestamps) Multi.run(multi, :tokens, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :tokens, + :tokens + ) end) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex b/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex index 99e9c275ddd2..19144b9dca97 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex @@ -9,6 +9,7 @@ defmodule Explorer.Chain.Import.Runner.Transaction.Forks do alias Ecto.{Multi, Repo} alias Explorer.Chain.{Hash, Import, Transaction} + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -43,7 +44,12 @@ defmodule Explorer.Chain.Import.Runner.Transaction.Forks do |> Map.put(:timestamps, timestamps) Multi.run(multi, :transaction_forks, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :forks, + :transaction_forks + ) end) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index 0d18c1f3e877..b0f94b7b1573 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -10,6 +10,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do alias Ecto.{Multi, Repo} alias Explorer.Chain.{Block, Hash, Import, Transaction} alias Explorer.Chain.Import.Runner.TokenTransfers + alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -45,10 +46,22 @@ defmodule Explorer.Chain.Import.Runner.Transactions do # Enforce ShareLocks tables order (see docs: sharelocks.md) multi |> Multi.run(:recollated_transactions, fn repo, _ -> - discard_blocks_for_recollated_transactions(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> + discard_blocks_for_recollated_transactions(repo, changes_list, insert_options) + end, + :block_referencing, + :transactions, + :recollated_transactions + ) end) |> Multi.run(:transactions, fn repo, _ -> - insert(repo, changes_list, insert_options) + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :transactions, + :transactions + ) end) end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 60cb8b89beb7..0b197ab152c3 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -120,8 +120,7 @@ defmodule Explorer.Chain.InternalTransaction do foreign_key: :block_hash, define_field: false, references: :block_hash, - type: Hash.Full, - where: [fetch_internal_transactions: true] + type: Hash.Full ) end @@ -253,7 +252,7 @@ defmodule Explorer.Chain.InternalTransaction do true Failed `:call`s are not allowed to set `gas_used` or `output` because they are part of the successful `result` object - in the Parity JSONRPC response. They still need `input`, however. + in the Nethermind JSONRPC response. They still need `input`, however. iex> changeset = Explorer.Chain.InternalTransaction.changeset( ...> %Explorer.Chain.InternalTransaction{}, @@ -281,8 +280,7 @@ defmodule Explorer.Chain.InternalTransaction do false iex> changeset.errors [ - output: {"can't be present for failed call", []}, - gas_used: {"can't be present for failed call", []} + output: {"can't be present for failed call", []} ] Likewise, successful `:call`s require `input`, `gas_used` and `output` to be set. @@ -315,7 +313,7 @@ defmodule Explorer.Chain.InternalTransaction do ] For failed `:create`, `created_contract_code`, `created_contract_address_hash`, and `gas_used` are not allowed to be - set because they come from `result` object, which shouldn't be returned from Parity. + set because they come from `result` object, which shouldn't be returned from Nethermind. iex> changeset = Explorer.Chain.InternalTransaction.changeset( ...> %Explorer.Chain.InternalTransaction{}, @@ -496,13 +494,11 @@ defmodule Explorer.Chain.InternalTransaction do end) end - @call_success_fields ~w(gas_used output)a - # Validates that :call `type` changeset either has an `error` or both `gas_used` and `output` defp validate_call_error_or_result(changeset) do case get_field(changeset, :error) do - nil -> validate_required(changeset, @call_success_fields, message: "can't be blank for successful call") - _ -> validate_disallowed(changeset, @call_success_fields, message: "can't be present for failed call") + nil -> validate_required(changeset, [:gas_used, :output], message: "can't be blank for successful call") + _ -> validate_disallowed(changeset, [:output], message: "can't be present for failed call") end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/action.ex b/apps/explorer/lib/explorer/chain/internal_transaction/action.ex index 663e30651501..d3311f34879e 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/action.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/action.ex @@ -1,6 +1,6 @@ defmodule Explorer.Chain.InternalTransaction.Action do @moduledoc """ - The action that was performed in a `t:EthereumJSONRPC.Parity.Trace.t/0` + The action that was performed in a `t:EthereumJSONRPC.Nethermind.Trace.t/0` """ import EthereumJSONRPC, only: [integer_to_quantity: 1] diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/result.ex b/apps/explorer/lib/explorer/chain/internal_transaction/result.ex index c4c680e7d445..505f54568630 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/result.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/result.ex @@ -1,6 +1,6 @@ defmodule Explorer.Chain.InternalTransaction.Result do @moduledoc """ - The result of performing the `t:EthereumJSONRPC.Parity.Action.t/0` in a `t:EthereumJSONRPC.Parity.Trace.t/0`. + The result of performing the `t:EthereumJSONRPC.Nethermind.Action.t/0` in a `t:EthereumJSONRPC.Nethermind.Trace.t/0`. """ import EthereumJSONRPC, only: [integer_to_quantity: 1] diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 68c227f1c6bc..4a2381888195 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -25,7 +25,7 @@ defmodule Explorer.Chain.Log do * `transaction` - transaction for which `log` is * `transaction_hash` - foreign key for `transaction`. * `index` - index of the log entry in all logs for the `transaction` - * `type` - type of event. *Parity-only* + * `type` - type of event. *Nethermind-only* """ @type t :: %__MODULE__{ address: %Ecto.Association.NotLoaded{} | Address.t(), @@ -128,8 +128,8 @@ defmodule Explorer.Chain.Log do ] case Chain.find_contract_address(log.address_hash, address_options, true) do - {:ok, %{smart_contract: %{abi: abi}}} -> - full_abi = Chain.combine_proxy_implementation_abi(log.address_hash, abi) + {:ok, %{smart_contract: smart_contract}} -> + full_abi = Chain.combine_proxy_implementation_abi(smart_contract) with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction), identifier <- Base.encode16(selector.method_id, case: :lower), diff --git a/apps/explorer/lib/explorer/chain/pending_block_operation.ex b/apps/explorer/lib/explorer/chain/pending_block_operation.ex index d85cc589b0b4..e5bfc0fc7112 100644 --- a/apps/explorer/lib/explorer/chain/pending_block_operation.ex +++ b/apps/explorer/lib/explorer/chain/pending_block_operation.ex @@ -7,21 +7,17 @@ defmodule Explorer.Chain.PendingBlockOperation do alias Explorer.Chain.{Block, Hash} - @required_attrs ~w(block_hash fetch_internal_transactions)a + @required_attrs ~w(block_hash)a @typedoc """ * `block_hash` - the hash of the block that has pending operations. - * `fetch_internal_transactions` - if the block needs its internal transactions fetched (or not) """ @type t :: %__MODULE__{ - block_hash: Hash.Full.t(), - fetch_internal_transactions: boolean() + block_hash: Hash.Full.t() } @primary_key false schema "pending_block_operations" do - field(:fetch_internal_transactions, :boolean) - timestamps() belongs_to(:block, Block, foreign_key: :block_hash, primary_key: true, references: :hash, type: Hash.Full) @@ -48,40 +44,10 @@ defmodule Explorer.Chain.PendingBlockOperation do ) end - def block_hashes(filter \\ nil) - - def block_hashes(filter) when is_nil(filter) do + def block_hashes do from( pending_ops in __MODULE__, select: pending_ops.block_hash ) end - - def block_hashes(filters) when is_list(filters) do - true_filters = Keyword.new(filters, &{&1, true}) - - from( - pending_ops in __MODULE__, - where: ^true_filters, - select: pending_ops.block_hash - ) - end - - def block_hashes(filter), do: block_hashes([filter]) - - def default_on_conflict do - from( - pending_ops in __MODULE__, - update: [ - set: [ - fetch_internal_transactions: - pending_ops.fetch_internal_transactions or fragment("EXCLUDED.fetch_internal_transactions"), - # Don't update `block_hash` as it is used for the conflict target - inserted_at: pending_ops.inserted_at, - updated_at: fragment("EXCLUDED.updated_at") - ] - ], - where: fragment("EXCLUDED.fetch_internal_transactions <> ?", pending_ops.fetch_internal_transactions) - ) - end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 13734a7a0a88..043077c1957c 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -13,9 +13,15 @@ defmodule Explorer.Chain.SmartContract do use Explorer.Schema alias Ecto.Changeset + alias EthereumJSONRPC.Contract + alias Explorer.Counters.AverageBlockTime alias Explorer.{Chain, Repo} alias Explorer.Chain.{Address, ContractMethod, DecompiledSmartContract, Hash} alias Explorer.Chain.SmartContract.ExternalLibrary + alias Explorer.SmartContract.Reader + alias Timex.Duration + + @burn_address_hash_str "0x0000000000000000000000000000000000000000" @typedoc """ The name of a parameter to a function or event. @@ -197,11 +203,15 @@ defmodule Explorer.Chain.SmartContract do * `partially_verified` - whether contract verified using partial matched source code or not. * `is_vyper_contract` - boolean flag, determines if contract is Vyper or not * `file_path` - show the filename or path to the file of the contract source file - * `is_changed_bytecode` - boolean flag, determines if contract's bytecode was modified + * `is_changed_bytecode` - boolean flag, determines if contract's bytecode was modified * `bytecode_checked_at` - timestamp of the last check of contract's bytecode matching (DB and BlockChain) * `contract_code_md5` - md5(`t:Explorer.Chain.Address.t/0` `contract_code`) * `implementation_name` - name of the proxy implementation + * `compiler_settings` - raw compilation parameters + * `implementation_fetched_at` - timestamp of the last fetching contract's implementation info + * `implementation_address_hash` - address hash of the proxy's implementation if any * `autodetect_constructor_args` - field was added for storing user's choice + * `is_yul` - field was added for storing user's choice """ @type t :: %Explorer.Chain.SmartContract{ @@ -221,7 +231,11 @@ defmodule Explorer.Chain.SmartContract do bytecode_checked_at: DateTime.t(), contract_code_md5: String.t(), implementation_name: String.t() | nil, - autodetect_constructor_args: boolean | nil + compiler_settings: map() | nil, + implementation_fetched_at: DateTime.t(), + implementation_address_hash: Hash.Address.t(), + autodetect_constructor_args: boolean | nil, + is_yul: boolean | nil } schema "smart_contracts" do @@ -242,7 +256,12 @@ defmodule Explorer.Chain.SmartContract do field(:bytecode_checked_at, :utc_datetime_usec, default: DateTime.add(DateTime.utc_now(), -86400, :second)) field(:contract_code_md5, :string) field(:implementation_name, :string) + field(:compiler_settings, :map) + field(:implementation_fetched_at, :utc_datetime_usec, default: nil) + field(:implementation_address_hash, Hash.Address, default: nil) field(:autodetect_constructor_args, :boolean, virtual: true) + field(:is_yul, :boolean, virtual: true) + field(:metadata_from_verified_twin, :boolean, virtual: true) has_many( :decompiled_smart_contracts, @@ -284,14 +303,16 @@ defmodule Explorer.Chain.SmartContract do :is_changed_bytecode, :bytecode_checked_at, :contract_code_md5, - :implementation_name + :implementation_name, + :compiler_settings, + :implementation_address_hash, + :implementation_fetched_at ]) |> validate_required([ :name, :compiler_version, :optimization, :contract_source_code, - :abi, :address_hash, :contract_code_md5 ]) @@ -410,7 +431,11 @@ defmodule Explorer.Chain.SmartContract do defp error_message(:unknown_error), do: "Unable to verify: unknown error." defp error_message(:deployed_bytecode), do: "Deployed bytecode does not correspond to contract creation code." + + defp error_message(string) when is_binary(string), do: string + defp error_message(_), do: "There was an error validating your contract, please try again." + defp error_message(:compilation, error_message), do: "There was an error compiling your contract: #{error_message}" defp select_error_field(:no_creation_data), do: :address_hash @@ -427,6 +452,7 @@ defmodule Explorer.Chain.SmartContract do %__MODULE__{} |> changeset(Map.from_struct(twin_contract)) |> Changeset.put_change(:autodetect_constructor_args, true) + |> Changeset.put_change(:is_yul, false) |> Changeset.force_change(:address_hash, Changeset.get_field(changeset, :address_hash)) end @@ -439,14 +465,15 @@ defmodule Explorer.Chain.SmartContract do |> Changeset.put_change(:compiler_version, "latest") |> Changeset.put_change(:contract_source_code, "") |> Changeset.put_change(:autodetect_constructor_args, true) + |> Changeset.put_change(:is_yul, false) end def merge_twin_vyper_contract_with_changeset( %__MODULE__{is_vyper_contract: true} = twin_contract, %Changeset{} = changeset ) do - twin_contract - |> changeset(%{}) + %__MODULE__{} + |> changeset(Map.from_struct(twin_contract)) |> Changeset.force_change(:address_hash, Changeset.get_field(changeset, :address_hash)) end @@ -454,6 +481,10 @@ defmodule Explorer.Chain.SmartContract do merge_twin_vyper_contract_with_changeset(nil, changeset) end + def merge_twin_vyper_contract_with_changeset(%__MODULE__{is_vyper_contract: nil}, %Changeset{} = changeset) do + merge_twin_vyper_contract_with_changeset(nil, changeset) + end + def merge_twin_vyper_contract_with_changeset(nil, %Changeset{} = changeset) do changeset |> Changeset.put_change(:name, "Vyper_contract") @@ -477,4 +508,385 @@ defmodule Explorer.Chain.SmartContract do end defp to_address_hash(address_hash), do: address_hash + + def proxy_contract?(%__MODULE__{abi: abi} = smart_contract) when not is_nil(abi) do + implementation_method_abi = + abi + |> Enum.find(fn method -> + Map.get(method, "name") == "implementation" || + Chain.master_copy_pattern?(method) + end) + + if implementation_method_abi || + not is_nil( + smart_contract + |> get_implementation_address_hash() + |> Tuple.to_list() + |> List.first() + ), + do: true, + else: false + end + + def proxy_contract?(_), do: false + + def get_implementation_address_hash(%__MODULE__{abi: nil}), do: {nil, nil} + + def get_implementation_address_hash(%__MODULE__{metadata_from_verified_twin: true} = smart_contract) do + get_implementation_address_hash({:updated, smart_contract}) + end + + def get_implementation_address_hash( + %__MODULE__{ + address_hash: address_hash, + implementation_fetched_at: implementation_fetched_at + } = smart_contract + ) do + updated_smart_contract = + if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) && + check_implementation_refetch_neccessity(implementation_fetched_at) do + Chain.address_hash_to_smart_contract_without_twin(address_hash) + else + smart_contract + end + + get_implementation_address_hash({:updated, updated_smart_contract}) + end + + def get_implementation_address_hash( + {:updated, + %__MODULE__{ + address_hash: address_hash, + abi: abi, + implementation_address_hash: implementation_address_hash_from_db, + implementation_name: implementation_name_from_db, + implementation_fetched_at: implementation_fetched_at, + metadata_from_verified_twin: metadata_from_verified_twin + }} + ) do + if check_implementation_refetch_neccessity(implementation_fetched_at) do + get_implementation_address_hash_task = + Task.async(fn -> get_implementation_address_hash(address_hash, abi, metadata_from_verified_twin) end) + + timeout = Application.get_env(:explorer, :implementation_data_fetching_timeout) + + case Task.yield(get_implementation_address_hash_task, timeout) || + Task.ignore(get_implementation_address_hash_task) do + {:ok, {:empty, :empty}} -> + {nil, nil} + + {:ok, {address_hash, _name} = result} when not is_nil(address_hash) -> + result + + _ -> + {db_implementation_data_converter(implementation_address_hash_from_db), + db_implementation_data_converter(implementation_name_from_db)} + end + else + {db_implementation_data_converter(implementation_address_hash_from_db), + db_implementation_data_converter(implementation_name_from_db)} + end + end + + def get_implementation_address_hash(_), do: {nil, nil} + + defp db_implementation_data_converter(nil), do: nil + defp db_implementation_data_converter(string) when is_binary(string), do: string + defp db_implementation_data_converter(other), do: to_string(other) + + defp check_implementation_refetch_neccessity(nil), do: true + + defp check_implementation_refetch_neccessity(timestamp) do + if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) do + now = DateTime.utc_now() + + average_block_time = + if Application.get_env(:explorer, :avg_block_time_as_ttl_cached_implementation_data_of_proxy) do + case AverageBlockTime.average_block_time() do + {:error, :disabled} -> + 0 + + duration -> + duration + |> Duration.to_milliseconds() + end + else + 0 + end + + fresh_time_distance = + case average_block_time do + 0 -> + Application.get_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy) + + time -> + round(time) + end + + timestamp + |> DateTime.add(fresh_time_distance, :millisecond) + |> DateTime.compare(now) != :gt + else + true + end + end + + @spec get_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil) :: + {String.t() | nil, String.t() | nil} + defp get_implementation_address_hash(proxy_address_hash, abi, metadata_from_verified_twin) + when not is_nil(proxy_address_hash) and not is_nil(abi) do + implementation_method_abi = + abi + |> Enum.find(fn method -> + Map.get(method, "name") == "implementation" && Map.get(method, "stateMutability") == "view" + end) + + master_copy_method_abi = + abi + |> Enum.find(fn method -> + Chain.master_copy_pattern?(method) + end) + + implementation_address = + cond do + implementation_method_abi -> + get_implementation_address_hash_basic(proxy_address_hash, abi) + + master_copy_method_abi -> + get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) + + true -> + get_implementation_address_hash_eip_1967(proxy_address_hash) + end + + save_implementation_data(implementation_address, proxy_address_hash, metadata_from_verified_twin) + end + + defp get_implementation_address_hash(_proxy_address_hash, _abi, _) do + {nil, nil} + end + + defp get_implementation_address_hash_eip_1967(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + # https://eips.ethereum.org/EIPS/eip-1967 + storage_slot_logic_contract_address = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" + + {_status, implementation_address} = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_logic_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000", nil] -> + fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + {:ok, implementation_logic_address} -> + {:ok, implementation_logic_address} + + _ -> + {:ok, nil} + end + + abi_decode_address_output(implementation_address) + end + + # changes requested by https://github.com/blockscout/blockscout/issues/4770 + # for support BeaconProxy pattern + defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do + # https://eips.ethereum.org/EIPS/eip-1967 + storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" + + implementation_method_abi = [ + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [] + } + ] + + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_beacon_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000", nil] -> + fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + {:ok, beacon_contract_address} -> + case beacon_contract_address + |> abi_decode_address_output() + |> get_implementation_address_hash_basic(implementation_method_abi) do + <> -> + {:ok, implementation_address} + + _ -> + {:ok, beacon_contract_address} + end + + _ -> + {:ok, nil} + end + end + + # changes requested by https://github.com/blockscout/blockscout/issues/5292 + defp fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do + # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation" + storage_slot_logic_contract_address = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" + + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_logic_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> + {:ok, "0x"} + + {:ok, logic_contract_address} -> + {:ok, logic_contract_address} + + _ -> + {:ok, nil} + end + end + + defp get_implementation_address_hash_basic(proxy_address_hash, abi) do + # 5c60da1b = keccak256(implementation()) + implementation_address = + case Reader.query_contract( + proxy_address_hash, + abi, + %{ + "5c60da1b" => [] + }, + false + ) do + %{"5c60da1b" => {:ok, [result]}} -> result + _ -> nil + end + + address_to_hex(implementation_address) + end + + defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + master_copy_storage_pointer = "0x0" + + {:ok, implementation_address} = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + master_copy_storage_pointer, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> + {:ok, "0x"} + + {:ok, logic_contract_address} -> + {:ok, logic_contract_address} + + _ -> + {:ok, nil} + end + + abi_decode_address_output(implementation_address) + end + + defp save_implementation_data(nil, _, _), do: {nil, nil} + + defp save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin) + when empty_address_hash_string in [ + "0x", + "0x0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + @burn_address_hash_str + ] do + if is_nil(metadata_from_verified_twin) or !metadata_from_verified_twin do + proxy_address_hash + |> Chain.address_hash_to_smart_contract_without_twin() + |> changeset(%{ + implementation_name: nil, + implementation_address_hash: nil, + implementation_fetched_at: DateTime.utc_now() + }) + |> Repo.update() + end + + {:empty, :empty} + end + + defp save_implementation_data(implementation_address_hash_string, proxy_address_hash, _) + when is_binary(implementation_address_hash_string) do + with {:ok, address_hash} <- Chain.string_to_address_hash(implementation_address_hash_string), + proxy_contract <- Chain.address_hash_to_smart_contract_without_twin(proxy_address_hash), + false <- is_nil(proxy_contract), + %{implementation: %__MODULE__{name: name}, proxy: proxy_contract} <- %{ + implementation: Chain.address_hash_to_smart_contract(address_hash), + proxy: proxy_contract + } do + proxy_contract + |> changeset(%{ + implementation_name: name, + implementation_address_hash: implementation_address_hash_string, + implementation_fetched_at: DateTime.utc_now() + }) + |> Repo.update() + + {implementation_address_hash_string, name} + else + %{implementation: _, proxy: proxy_contract} -> + proxy_contract + |> changeset(%{ + implementation_name: nil, + implementation_address_hash: implementation_address_hash_string, + implementation_fetched_at: DateTime.utc_now() + }) + |> Repo.update() + + {implementation_address_hash_string, nil} + + true -> + {:ok, address_hash} = Chain.string_to_address_hash(implementation_address_hash_string) + smart_contract = Chain.address_hash_to_smart_contract(address_hash) + + {implementation_address_hash_string, smart_contract && smart_contract.name} + + _ -> + {implementation_address_hash_string, nil} + end + end + + defp address_to_hex(address) do + if address do + if String.starts_with?(address, "0x") do + address + else + "0x" <> Base.encode16(address, case: :lower) + end + end + end + + defp abi_decode_address_output(nil), do: nil + + defp abi_decode_address_output("0x"), do: @burn_address_hash_str + + defp abi_decode_address_output(address) when is_binary(address) do + if String.length(address) > 42 do + "0x" <> String.slice(address, -40, 40) + else + address + end + end + + defp abi_decode_address_output(_), do: nil end diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 1a6883492bd2..f4f9c424e948 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -5,8 +5,9 @@ defmodule Explorer.Chain.Token.Instance do use Explorer.Schema - alias Explorer.Chain.{Hash, Token} + alias Explorer.Chain.{Address, Hash, Token, TokenTransfer} alias Explorer.Chain.Token.Instance + alias Explorer.PagingOptions @typedoc """ * `token_id` - ID of the token @@ -18,7 +19,7 @@ defmodule Explorer.Chain.Token.Instance do @type t :: %Instance{ token_id: non_neg_integer(), token_contract_address_hash: Hash.Address.t(), - metadata: map(), + metadata: map() | nil, error: String.t() } @@ -28,6 +29,8 @@ defmodule Explorer.Chain.Token.Instance do field(:metadata, :map) field(:error, :string) + belongs_to(:owner, Address, references: :hash, define_field: false) + belongs_to( :token, Token, @@ -46,4 +49,44 @@ defmodule Explorer.Chain.Token.Instance do |> validate_required([:token_id, :token_contract_address_hash]) |> foreign_key_constraint(:token_contract_address_hash) end + + @doc """ + Inventory tab query. + A token ERC-721 is considered unique because it corresponds to the possession + of a specific asset. + + To find out its current owner, it is necessary to look at the token last + transfer. + """ + + def address_to_unique_token_instances(contract_address_hash) do + from( + i in Instance, + where: i.token_contract_address_hash == ^contract_address_hash, + order_by: [desc: i.token_id] + ) + end + + def page_token_instance(query, %PagingOptions{key: {token_id}, asc_order: true}) do + where(query, [i], i.token_id > ^token_id) + end + + def page_token_instance(query, %PagingOptions{key: {token_id}}) do + where(query, [i], i.token_id < ^token_id) + end + + def page_token_instance(query, _), do: query + + def owner_query(%Instance{token_contract_address_hash: token_contract_address_hash, token_id: token_id}) do + from( + tt in TokenTransfer, + join: to_address in assoc(tt, :to_address), + where: + tt.token_contract_address_hash == ^token_contract_address_hash and + fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id), + order_by: [desc: tt.block_number], + limit: 1, + select: to_address + ) + end end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index fe17971fb53e..e057bdaa5166 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -25,7 +25,7 @@ defmodule Explorer.Chain.TokenTransfer do use Explorer.Schema import Ecto.Changeset - import Ecto.Query, only: [from: 2, limit: 2, where: 3] + import Ecto.Query, only: [from: 2, limit: 2, where: 3, join: 5, order_by: 3, preload: 3] alias Explorer.Chain.{Address, Block, Hash, TokenTransfer, Transaction} alias Explorer.Chain.Token.Instance @@ -110,8 +110,8 @@ defmodule Explorer.Chain.TokenTransfer do type: Hash.Full ) - has_one( - :instance, + has_many( + :instances, Instance, foreign_key: :token_contract_address_hash, references: :token_contract_address_hash @@ -177,7 +177,7 @@ defmodule Explorer.Chain.TokenTransfer do from( tt in TokenTransfer, where: tt.token_contract_address_hash == ^token_address_hash, - where: tt.token_id == ^token_id, + where: fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)), where: not is_nil(tt.block_number), preload: [{:transaction, :block}, :token, :from_address, :to_address], order_by: [desc: tt.block_number] @@ -206,7 +206,9 @@ defmodule Explorer.Chain.TokenTransfer do query = from( tt in TokenTransfer, - where: tt.token_contract_address_hash == ^token_address_hash and tt.token_id == ^token_id, + where: + tt.token_contract_address_hash == ^token_address_hash and + fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)), select: fragment("COUNT(*)") ) @@ -216,11 +218,11 @@ defmodule Explorer.Chain.TokenTransfer do def page_token_transfer(query, %PagingOptions{key: nil}), do: query def page_token_transfer(query, %PagingOptions{key: {token_id}, asc_order: true}) do - where(query, [tt], tt.token_id > ^token_id) + where(query, [tt], fragment("?[1] > ?", tt.token_ids, ^token_id)) end def page_token_transfer(query, %PagingOptions{key: {token_id}}) do - where(query, [tt], tt.token_id < ^token_id) + where(query, [tt], fragment("?[1] < ?", tt.token_ids, ^token_id)) end def page_token_transfer(query, %PagingOptions{key: {block_number, log_index}, asc_order: true}) do @@ -239,6 +241,16 @@ defmodule Explorer.Chain.TokenTransfer do ) end + def handle_paging_options(query, nil), do: query + + def handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query + + def handle_paging_options(query, paging_options) do + query + |> page_token_transfer(paging_options) + |> limit(^paging_options.page_size) + end + @doc """ Fetches the transaction hashes from token transfers according to the address hash. @@ -303,26 +315,35 @@ defmodule Explorer.Chain.TokenTransfer do ) end - @doc """ - Innventory tab query. - A token ERC-721 is considered unique because it corresponds to the possession - of a specific asset. + def token_transfers_by_address_hash(direction, address_hash, token_types) do + TokenTransfer + |> filter_by_direction(direction, address_hash) + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) + |> join(:inner, [tt], token in assoc(tt, :token), as: :token) + |> preload([token: token], [{:token, token}]) + |> filter_by_type(token_types) + end - To find out its current owner, it is necessary to look at the token last - transfer. - """ - @spec address_to_unique_tokens(Hash.Address.t()) :: Ecto.Query.t() - def address_to_unique_tokens(contract_address_hash) do - from( - tt in TokenTransfer, - left_join: instance in Instance, - on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id, - where: tt.token_contract_address_hash == ^contract_address_hash, - where: tt.to_address_hash != ^"0x0000000000000000000000000000000000000000", - order_by: [desc: tt.block_number], - distinct: [desc: tt.token_id], - preload: [:to_address], - select: %{tt | instance: instance} - ) + def filter_by_direction(query, :to, address_hash) do + query + |> where([tt], tt.to_address_hash == ^address_hash) + end + + def filter_by_direction(query, :from, address_hash) do + query + |> where([tt], tt.from_address_hash == ^address_hash) end + + def filter_by_direction(query, _, address_hash) do + query + |> where([tt], tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash) + end + + def filter_by_type(query, []), do: query + + def filter_by_type(query, token_types) when is_list(token_types) do + where(query, [token: token], token.type in ^token_types) + end + + def filter_by_type(query, _), do: query end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 88696c91c3aa..0dccd1c90f90 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -9,6 +9,7 @@ defmodule Explorer.Chain.Transaction do alias ABI.FunctionSelector + alias Ecto.Association.NotLoaded alias Ecto.Changeset alias Explorer.{Chain, Repo} @@ -22,6 +23,7 @@ defmodule Explorer.Chain.Transaction do Hash, InternalTransaction, Log, + SmartContract, TokenTransfer, Transaction, Wei @@ -464,6 +466,18 @@ defmodule Explorer.Chain.Transaction do def decoded_input_data(%__MODULE__{input: %{bytes: bytes}}) when bytes in [nil, <<>>], do: {:error, :no_input_data} def decoded_input_data(%__MODULE__{to_address: %{contract_code: nil}}), do: {:error, :not_a_contract_call} + def decoded_input_data(%__MODULE__{ + to_address: %{smart_contract: %NotLoaded{}}, + input: input, + hash: hash + }) do + decoded_input_data(%__MODULE__{ + to_address: %{smart_contract: nil}, + input: input, + hash: hash + }) + end + def decoded_input_data(%__MODULE__{ to_address: %{smart_contract: nil}, input: %{bytes: <> = data}, @@ -480,7 +494,7 @@ defmodule Explorer.Chain.Transaction do candidates_query |> Repo.all() |> Enum.flat_map(fn candidate -> - case do_decoded_input_data(data, [candidate.abi], nil, hash) do + case do_decoded_input_data(data, %SmartContract{abi: [candidate.abi], address_hash: nil}, hash) do {:ok, _, _, _} = decoded -> [decoded] _ -> [] end @@ -495,10 +509,10 @@ defmodule Explorer.Chain.Transaction do def decoded_input_data(%__MODULE__{ input: %{bytes: data}, - to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}}, + to_address: %{smart_contract: smart_contract}, hash: hash }) do - case do_decoded_input_data(data, abi, address_hash, hash) do + case do_decoded_input_data(data, smart_contract, hash) do # In some cases transactions use methods of some unpredictadle contracts, so we can try to look up for method in a whole DB {:error, :could_not_decode} -> case decoded_input_data(%__MODULE__{ @@ -521,8 +535,8 @@ defmodule Explorer.Chain.Transaction do end end - defp do_decoded_input_data(data, abi, address_hash, hash) do - full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi) + defp do_decoded_input_data(data, smart_contract, hash) do + full_abi = Chain.combine_proxy_implementation_abi(smart_contract) with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash), {:ok, mapping} <- selector_mapping(selector, values, hash), @@ -558,14 +572,16 @@ defmodule Explorer.Chain.Transaction do def get_method_name(_), do: "Transfer" - defp parse_method_name(method_desc) do + def parse_method_name(method_desc, need_upcase \\ true) do method_desc |> String.split("(") |> Enum.at(0) - |> upcase_first + |> upcase_first(need_upcase) end - defp upcase_first(<>), do: String.upcase(<>) <> rest + defp upcase_first(string, false), do: string + + defp upcase_first(<>, true), do: String.upcase(<>) <> rest defp function_call(name, mapping) do text = diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index b77f71f90f4d..533174ba4524 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -266,3 +266,10 @@ defimpl Inspect, for: Explorer.Chain.Wei do "#Explorer.Chain.Wei<#{Decimal.to_string(wei.value)}>" end end + +defimpl Jason.Encoder, for: Explorer.Chain.Wei do + def encode(wei, opts) do + # changed since it's needed to return wei value (which is big number) as string + Jason.Encode.struct(wei.value, opts) + end +end diff --git a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex index a67036fa4213..e1baf16c6845 100644 --- a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex +++ b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex @@ -66,7 +66,7 @@ defmodule Explorer.ChainSpec.GenesisData do case fetch_spec(path) do {:ok, chain_spec} -> case variant do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> Importer.import_emission_rewards(chain_spec) {:ok, _} = Importer.import_genesis_accounts(chain_spec) diff --git a/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex b/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex index 2572360f8eaf..9373d4eb6f27 100644 --- a/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex +++ b/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex @@ -11,7 +11,7 @@ defmodule Explorer.Counters.AddressTransactionsGasUsageCounter do @cache_name :address_transactions_gas_usage_counter @last_update_key "last_update" - config = Application.get_env(:explorer, __MODULE__) + config = Application.compile_env(:explorer, __MODULE__) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex b/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex index 409030e18bf4..5b17ebe70bdc 100644 --- a/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex +++ b/apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex @@ -11,7 +11,7 @@ defmodule Explorer.Counters.AddressTokenTransfersCounter do @cache_name :address_token_transfers_counter @last_update_key "last_update" - config = Application.get_env(:explorer, __MODULE__) + config = Application.compile_env(:explorer, __MODULE__) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex b/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex index e0e5f9c9a268..12dfb1e0cbea 100644 --- a/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex +++ b/apps/explorer/lib/explorer/counters/address_tokens_usd_sum.ex @@ -10,7 +10,7 @@ defmodule Explorer.Counters.AddressTokenUsdSum do @cache_name :address_tokens_usd_value @last_update_key "last_update" - config = Application.get_env(:explorer, Explorer.Counters.AddressTokenUsdSum) + config = Application.compile_env(:explorer, Explorer.Counters.AddressTokenUsdSum) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() @@ -50,6 +50,18 @@ defmodule Explorer.Counters.AddressTokenUsdSum do fetch_from_cache("hash_#{address_hash_string}") end + @spec address_tokens_usd_sum([{Address.CurrentTokenBalance, Explorer.Chain.Token}]) :: Decimal.t() + defp address_tokens_usd_sum(token_balances) do + token_balances + |> Enum.reduce(Decimal.new(0), fn {token_balance, token}, acc -> + if token_balance.value && token.usd_value do + Decimal.add(acc, Chain.balance_in_usd(token_balance, token)) + else + acc + end + end) + end + def cache_name, do: @cache_name defp cache_expired?(address_hash_string) do @@ -65,7 +77,7 @@ defmodule Explorer.Counters.AddressTokenUsdSum do defp update_cache(address_hash_string, token_balances) do put_into_cache("hash_#{address_hash_string}_#{@last_update_key}", Helper.current_time()) - new_data = Chain.address_tokens_usd_sum(token_balances) + new_data = address_tokens_usd_sum(token_balances) put_into_cache("hash_#{address_hash_string}", new_data) end diff --git a/apps/explorer/lib/explorer/counters/address_transactions_counter.ex b/apps/explorer/lib/explorer/counters/address_transactions_counter.ex index bcd0a71207eb..275cf50c24ce 100644 --- a/apps/explorer/lib/explorer/counters/address_transactions_counter.ex +++ b/apps/explorer/lib/explorer/counters/address_transactions_counter.ex @@ -11,7 +11,7 @@ defmodule Explorer.Counters.AddressTransactionsCounter do @cache_name :address_transactions_counter @last_update_key "last_update" - config = Application.get_env(:explorer, __MODULE__) + config = Application.compile_env(:explorer, __MODULE__) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/addresses_counter.ex b/apps/explorer/lib/explorer/counters/addresses_counter.ex index 5febd8fbb9d4..0ed6361f47b0 100644 --- a/apps/explorer/lib/explorer/counters/addresses_counter.ex +++ b/apps/explorer/lib/explorer/counters/addresses_counter.ex @@ -26,7 +26,7 @@ defmodule Explorer.Counters.AddressesCounter do # finish before a test ends, that test will fail. This way, hundreds of # tests were failing before disabling the consolidation and the scheduler in # the test env. - config = Application.get_env(:explorer, Explorer.Counters.AddressesCounter) + config = Application.compile_env(:explorer, Explorer.Counters.AddressesCounter) @enable_consolidation Keyword.get(config, :enable_consolidation) @update_interval_in_seconds Keyword.get(config, :update_interval_in_seconds) diff --git a/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex b/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex index cdc2530f93b8..404091b60881 100644 --- a/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex +++ b/apps/explorer/lib/explorer/counters/addresses_with_balance_counter.ex @@ -26,7 +26,7 @@ defmodule Explorer.Counters.AddressesWithBalanceCounter do # finish before a test ends, that test will fail. This way, hundreds of # tests were failing before disabling the consolidation and the scheduler in # the test env. - config = Application.get_env(:explorer, Explorer.Counters.AddressesWithBalanceCounter) + config = Application.compile_env(:explorer, Explorer.Counters.AddressesWithBalanceCounter) @enable_consolidation Keyword.get(config, :enable_consolidation) @update_interval_in_seconds Keyword.get(config, :update_interval_in_seconds) diff --git a/apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex b/apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex index 84444cc5c371..46be83a39ffb 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex @@ -36,7 +36,7 @@ defmodule Explorer.Counters.AverageBlockTimeDurationFormat do "1.1 minutes" """ @spec format(Duration.t()) :: String.t() | {:error, term} - def format(%Duration{} = duration), do: lformat(duration, Translator.default_locale()) + def format(%Duration{} = duration), do: lformat(duration, Translator.current_locale()) def format(_), do: {:error, :invalid_duration} @doc """ @@ -88,13 +88,17 @@ defmodule Explorer.Counters.AverageBlockTimeDurationFormat do truncated = trunc(decimal_value) # remove any trailing `.0` - formatted_value = - if decimal_value == truncated do - truncated - else - Float.round(decimal_value, 1) - end - - Translator.translate_plural(locale, "units", "%{count} #{singular}", "%{count} #{singular}s", formatted_value) + if decimal_value == truncated do + Translator.translate_plural(locale, "units", "%{count} #{singular}", "%{count} #{singular}s", truncated) + else + value = + decimal_value + |> Float.round(1) + |> :erlang.float_to_binary(decimals: 1) + + locale + |> Translator.translate_plural("units", "%{count} #{singular}", "%{count} #{singular}s", 5) + |> String.replace("5", value) + end end end diff --git a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex index 5726bf6e0a55..85e9359f4294 100644 --- a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex @@ -9,7 +9,7 @@ defmodule Explorer.Counters.BlockBurnedFeeCounter do @cache_name :block_burned_fee_counter - config = Application.get_env(:explorer, Explorer.Counters.BlockBurnedFeeCounter) + config = Application.compile_env(:explorer, Explorer.Counters.BlockBurnedFeeCounter) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex index 54df5a858204..f51deb4e2e7a 100644 --- a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex @@ -9,7 +9,7 @@ defmodule Explorer.Counters.BlockPriorityFeeCounter do @cache_name :block_priority_fee_counter - config = Application.get_env(:explorer, Explorer.Counters.BlockPriorityFeeCounter) + config = Application.compile_env(:explorer, Explorer.Counters.BlockPriorityFeeCounter) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/token_holders_counter.ex b/apps/explorer/lib/explorer/counters/token_holders_counter.ex index 2959f59f0d6b..a39e2e409eae 100644 --- a/apps/explorer/lib/explorer/counters/token_holders_counter.ex +++ b/apps/explorer/lib/explorer/counters/token_holders_counter.ex @@ -10,7 +10,7 @@ defmodule Explorer.Counters.TokenHoldersCounter do @cache_name :token_holders_counter @last_update_key "last_update" - config = Application.get_env(:explorer, Explorer.Counters.TokenHoldersCounter) + config = Application.compile_env(:explorer, Explorer.Counters.TokenHoldersCounter) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/token_transfers_counter.ex b/apps/explorer/lib/explorer/counters/token_transfers_counter.ex index 8ed6d9effadc..0bf2d024020d 100644 --- a/apps/explorer/lib/explorer/counters/token_transfers_counter.ex +++ b/apps/explorer/lib/explorer/counters/token_transfers_counter.ex @@ -10,7 +10,7 @@ defmodule Explorer.Counters.TokenTransfersCounter do @cache_name :token_transfers_counter @last_update_key "last_update" - config = Application.get_env(:explorer, Explorer.Counters.TokenTransfersCounter) + config = Application.compile_env(:explorer, Explorer.Counters.TokenTransfersCounter) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/encrypted/address_hash.ex b/apps/explorer/lib/explorer/encrypted/address_hash.ex new file mode 100644 index 000000000000..4518951298ee --- /dev/null +++ b/apps/explorer/lib/explorer/encrypted/address_hash.ex @@ -0,0 +1,5 @@ +defmodule Explorer.Encrypted.AddressHash do + @moduledoc false + + use Explorer.Encrypted.Types.AddressHash, vault: Explorer.Vault +end diff --git a/apps/explorer/lib/explorer/encrypted/binary.ex b/apps/explorer/lib/explorer/encrypted/binary.ex new file mode 100644 index 000000000000..6de296ded69d --- /dev/null +++ b/apps/explorer/lib/explorer/encrypted/binary.ex @@ -0,0 +1,5 @@ +defmodule Explorer.Encrypted.Binary do + @moduledoc false + + use Cloak.Ecto.Binary, vault: Explorer.Vault +end diff --git a/apps/explorer/lib/explorer/encrypted/transaction_hash.ex b/apps/explorer/lib/explorer/encrypted/transaction_hash.ex new file mode 100644 index 000000000000..a783cb899b2b --- /dev/null +++ b/apps/explorer/lib/explorer/encrypted/transaction_hash.ex @@ -0,0 +1,5 @@ +defmodule Explorer.Encrypted.TransactionHash do + @moduledoc false + + use Explorer.Encrypted.Types.TransactionHash, vault: Explorer.Vault +end diff --git a/apps/explorer/lib/explorer/encrypted/types/address_hash.ex b/apps/explorer/lib/explorer/encrypted/types/address_hash.ex new file mode 100644 index 000000000000..f9ca332c538f --- /dev/null +++ b/apps/explorer/lib/explorer/encrypted/types/address_hash.ex @@ -0,0 +1,27 @@ +defmodule Explorer.Encrypted.Types.AddressHash do + @moduledoc """ + An `Ecto.Type` to encrypt address_hash fields. + """ + + @doc false + defmacro __using__(opts) do + opts = Keyword.merge(opts, vault: Keyword.fetch!(opts, :vault)) + + quote do + use Cloak.Ecto.Type, unquote(opts) + + def cast(value) do + Explorer.Chain.Hash.Address.cast(value) + end + + def after_decrypt(nil), do: nil + def after_decrypt(""), do: nil + def after_decrypt(:error), do: nil + + def after_decrypt(value) do + {:ok, address_hash} = Explorer.Chain.Hash.Address.cast(value) + address_hash + end + end + end +end diff --git a/apps/explorer/lib/explorer/encrypted/types/transaction_hash.ex b/apps/explorer/lib/explorer/encrypted/types/transaction_hash.ex new file mode 100644 index 000000000000..7c39a9aeca57 --- /dev/null +++ b/apps/explorer/lib/explorer/encrypted/types/transaction_hash.ex @@ -0,0 +1,27 @@ +defmodule Explorer.Encrypted.Types.TransactionHash do + @moduledoc """ + An `Ecto.Type` to encrypt transaction_hash fields. + """ + + @doc false + defmacro __using__(opts) do + opts = Keyword.merge(opts, vault: Keyword.fetch!(opts, :vault)) + + quote do + use Cloak.Ecto.Type, unquote(opts) + + def cast(value) do + Explorer.Chain.Hash.Full.cast(value) + end + + def after_decrypt(nil), do: nil + def after_decrypt(""), do: nil + def after_decrypt(:error), do: nil + + def after_decrypt(value) do + {:ok, transaction_hash} = Explorer.Chain.Hash.Full.cast(value) + transaction_hash + end + end + end +end diff --git a/apps/explorer/lib/explorer/env_var_translator.ex b/apps/explorer/lib/explorer/env_var_translator.ex new file mode 100644 index 000000000000..62f3da9003a0 --- /dev/null +++ b/apps/explorer/lib/explorer/env_var_translator.ex @@ -0,0 +1,24 @@ +defmodule Explorer.EnvVarTranslator do + @moduledoc """ + The module for transaformation of environment variables + """ + + alias Poison.Parser + + @spec map_array_env_var_to_list(atom()) :: list() + def map_array_env_var_to_list(config_name) do + env_var = Application.get_env(:block_scout_web, config_name) + + if env_var do + try do + env_var + |> Parser.parse!(%{keys: :atoms!}) + rescue + _ -> + [] + end + else + [] + end + end +end diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index df07914f33ac..fa3a41188037 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -80,6 +80,7 @@ defmodule Explorer.Etherscan do created_contract_address_hash input type + call_type gas gas_used error @@ -201,6 +202,14 @@ defmodule Explorer.Etherscan do query_to_address_hash_wrapped |> union(^query_from_address_hash_wrapped) |> union(^query_created_contract_address_hash_wrapped) + |> Chain.wrapped_union_subquery() + |> order_by( + [q], + [ + {^options.order_by_direction, q.block_number}, + desc: q.index + ] + ) |> Repo.replica().all() else query = @@ -430,6 +439,7 @@ defmodule Explorer.Etherscan do from_address_hash to_address_hash amount + amounts )a defp list_token_transfers(address_hash, contract_address_hash, block_height, options) do @@ -444,7 +454,7 @@ defmodule Explorer.Etherscan do offset: ^offset(options), select: merge(map(tt, ^@token_transfer_fields), %{ - token_id: tt.token_id, + token_ids: tt.token_ids, token_name: tkn.name, token_symbol: tkn.symbol, token_decimals: tkn.decimals, @@ -470,6 +480,7 @@ defmodule Explorer.Etherscan do from_address_hash: tt.from_address_hash, to_address_hash: tt.to_address_hash, amount: tt.amount, + amounts: tt.amounts, transaction_nonce: t.nonce, transaction_index: t.index, transaction_gas: t.gas, @@ -481,7 +492,7 @@ defmodule Explorer.Etherscan do block_number: b.number, block_timestamp: b.timestamp, confirmations: fragment("? - ?", ^block_height, t.block_number), - token_id: tt.token_id, + token_ids: tt.token_ids, token_name: tt.token_name, token_symbol: tt.token_symbol, token_decimals: tt.token_decimals, diff --git a/apps/explorer/lib/explorer/etherscan/addresses.ex b/apps/explorer/lib/explorer/etherscan/addresses.ex index beb69d051d38..999d34ca6a69 100644 --- a/apps/explorer/lib/explorer/etherscan/addresses.ex +++ b/apps/explorer/lib/explorer/etherscan/addresses.ex @@ -18,7 +18,7 @@ defmodule Explorer.Etherscan.Addresses do query = from( address in Address, - order_by: [asc: address.inserted_at], + order_by: [asc: address.inserted_at, asc: address.hash], offset: ^offset, limit: ^limit ) diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex index 37e8ef7a34a9..cea04475df1d 100644 --- a/apps/explorer/lib/explorer/etherscan/contracts.ex +++ b/apps/explorer/lib/explorer/etherscan/contracts.ex @@ -7,7 +7,8 @@ defmodule Explorer.Etherscan.Contracts do import Ecto.Query, only: [ - from: 2 + from: 2, + where: 3 ] alias Explorer.{Chain, Repo} @@ -59,13 +60,13 @@ defmodule Explorer.Etherscan.Contracts do def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do updated_smart_contract = - if Chain.proxy_contract?(address.hash, smart_contract.abi) do + if SmartContract.proxy_contract?(smart_contract) do smart_contract |> Map.put(:is_proxy, true) |> Map.put( :implementation_address_hash_string, - address.hash - |> Chain.get_implementation_address_hash(smart_contract.abi) + smart_contract + |> SmartContract.get_implementation_address_hash() |> Tuple.to_list() |> List.first() ) @@ -80,7 +81,7 @@ defmodule Explorer.Etherscan.Contracts do def append_proxy_info(address), do: address - def list_verified_contracts(limit, offset) do + def list_verified_contracts(limit, offset, opts) do query = from( smart_contract in SmartContract, @@ -90,7 +91,29 @@ defmodule Explorer.Etherscan.Contracts do preload: [:address] ) - query + verified_at_start_timestamp_exist? = Map.has_key?(opts, :verified_at_start_timestamp) + verified_at_end_timestamp_exist? = Map.has_key?(opts, :verified_at_end_timestamp) + + query_in_timestamp_range = + cond do + verified_at_start_timestamp_exist? && verified_at_end_timestamp_exist? -> + query + |> where([smart_contract], smart_contract.inserted_at >= ^opts.verified_at_start_timestamp) + |> where([smart_contract], smart_contract.inserted_at < ^opts.verified_at_end_timestamp) + + verified_at_start_timestamp_exist? -> + query + |> where([smart_contract], smart_contract.inserted_at >= ^opts.verified_at_start_timestamp) + + verified_at_end_timestamp_exist? -> + query + |> where([smart_contract], smart_contract.inserted_at < ^opts.verified_at_end_timestamp) + + true -> + query + end + + query_in_timestamp_range |> Repo.replica().all() |> Enum.map(fn smart_contract -> Map.put(smart_contract.address, :smart_contract, smart_contract) diff --git a/apps/explorer/lib/explorer/exchange_rates/token.ex b/apps/explorer/lib/explorer/exchange_rates/token.ex index 6521181c1234..8aa6d3b721aa 100644 --- a/apps/explorer/lib/explorer/exchange_rates/token.ex +++ b/apps/explorer/lib/explorer/exchange_rates/token.ex @@ -30,6 +30,7 @@ defmodule Explorer.ExchangeRates.Token do volume_24h_usd: Decimal.t() } + @derive Jason.Encoder @enforce_keys ~w(available_supply total_supply btc_value id last_updated market_cap_usd name symbol usd_value volume_24h_usd)a defstruct ~w(available_supply total_supply btc_value id last_updated market_cap_usd name symbol usd_value volume_24h_usd)a diff --git a/apps/explorer/lib/explorer/graphql.ex b/apps/explorer/lib/explorer/graphql.ex index e892ef584ac8..aa341e095f9a 100644 --- a/apps/explorer/lib/explorer/graphql.ex +++ b/apps/explorer/lib/explorer/graphql.ex @@ -24,15 +24,15 @@ defmodule Explorer.GraphQL do Returns a query to fetch transactions with a matching `to_address_hash`, `from_address_hash`, or `created_contract_address_hash` field for a given address hash. - Orders transactions by descending block number and index. + Orders transactions by `block_number` and `index` according to to `order` """ - @spec address_to_transactions_query(Hash.Address.t()) :: Ecto.Query.t() - def address_to_transactions_query(address_hash) do + @spec address_to_transactions_query(Hash.Address.t(), :desc | :asc) :: Ecto.Query.t() + def address_to_transactions_query(address_hash, order) do Transaction - |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) |> where([transaction], transaction.to_address_hash == ^address_hash) |> or_where([transaction], transaction.from_address_hash == ^address_hash) |> or_where([transaction], transaction.created_contract_address_hash == ^address_hash) + |> order_by([transaction], [{^order, transaction.block_number}, {^order, transaction.index}]) end @doc """ diff --git a/apps/explorer/lib/explorer/mailer.ex b/apps/explorer/lib/explorer/mailer.ex new file mode 100644 index 000000000000..ba5c095714bc --- /dev/null +++ b/apps/explorer/lib/explorer/mailer.ex @@ -0,0 +1,12 @@ +defmodule Explorer.Mailer do + @moduledoc """ + Base module for mail sending + + add in your module: + alias Explorer.Mailer + + and call + Mailer.deliver_now!(email) + """ + use Bamboo.Mailer, otp_app: :explorer +end diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 01a815cf8c8d..150adb1a738c 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -76,7 +76,7 @@ defmodule Explorer.Market do Enum.map(tokens, fn item -> case item do {token_balance, token} -> - {add_price(token_balance), token} + {token_balance, add_price(token)} token_balance -> add_price(token_balance) diff --git a/apps/explorer/lib/explorer/prometheus/instrumenter.ex b/apps/explorer/lib/explorer/prometheus/instrumenter.ex new file mode 100644 index 000000000000..c3ae1950122c --- /dev/null +++ b/apps/explorer/lib/explorer/prometheus/instrumenter.ex @@ -0,0 +1,23 @@ +defmodule Explorer.Prometheus.Instrumenter do + @moduledoc """ + Blocks fetch and import metrics for `Prometheus`. + """ + + use Prometheus.Metric + + @histogram [ + name: :block_import_stage_runner_duration_microseconds, + labels: [:stage, :runner, :step], + buckets: [1000, 5000, 10000, 100_000], + duration_unit: :microseconds, + help: "Block import stage, runner and step in runner processing time" + ] + + def block_import_stage_runner(function, stage, runner, step) do + {time, result} = :timer.tc(function) + + Histogram.observe([name: :block_import_stage_runner_duration_microseconds, labels: [stage, runner, step]], time) + + result + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 14a48d9d335b..c64db8b97d21 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -128,10 +128,54 @@ defmodule Explorer.Repo do def replica, do: Explorer.Repo.Replica1 end + def account_repo, do: Explorer.Repo.Account + defmodule Replica1 do use Ecto.Repo, otp_app: :explorer, adapter: Ecto.Adapters.Postgres, read_only: true + + def init(_, opts) do + db_url = Application.get_env(:explorer, Explorer.Repo.Replica1)[:url] + repo_conf = Application.get_env(:explorer, Explorer.Repo.Replica1) + + merged = + %{url: db_url} + |> ConfigHelper.get_db_config() + |> Keyword.merge(repo_conf, fn + _key, v1, nil -> v1 + _key, nil, v2 -> v2 + _, _, v2 -> v2 + end) + + Application.put_env(:explorer, Explorer.Repo.Replica1, merged) + + {:ok, Keyword.put(opts, :url, db_url)} + end + end + + defmodule Account do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + db_url = Application.get_env(:explorer, Explorer.Repo.Account)[:url] + repo_conf = Application.get_env(:explorer, Explorer.Repo.Account) + + merged = + %{url: db_url} + |> ConfigHelper.get_db_config() + |> Keyword.merge(repo_conf, fn + _key, v1, nil -> v1 + _key, nil, v2 -> v2 + _, _, v2 -> v2 + end) + + Application.put_env(:explorer, Explorer.Repo.Account, merged) + + {:ok, Keyword.put(opts, :url, db_url)} + end end end diff --git a/apps/explorer/lib/explorer/repo/config_helper.ex b/apps/explorer/lib/explorer/repo/config_helper.ex index fee789a2b31d..c63313a7f9cf 100644 --- a/apps/explorer/lib/explorer/repo/config_helper.ex +++ b/apps/explorer/lib/explorer/repo/config_helper.ex @@ -25,11 +25,24 @@ defmodule Explorer.Repo.ConfigHelper do |> Keyword.merge(extract_parameters(url)) end + def get_db_pool_size(default_pool_size), do: String.to_integer(System.get_env("POOL_SIZE", default_pool_size)) + + def get_account_db_url, do: System.get_env("ACCOUNT_DATABASE_URL") || System.get_env("DATABASE_URL") + + def get_account_db_pool_size(default_pool_size), + do: String.to_integer(System.get_env("ACCOUNT_POOL_SIZE", default_pool_size)) + + def get_api_db_url, do: System.get_env("DATABASE_READ_ONLY_API_URL") || System.get_env("DATABASE_URL") + + def get_api_db_pool_size(default_pool_size), do: String.to_integer(System.get_env("POOL_SIZE_API", default_pool_size)) + + def ssl_enabled?, do: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true") + defp extract_parameters(empty) when empty == nil or empty == "", do: [] # sobelow_skip ["DOS.StringToAtom"] defp extract_parameters(database_url) do - ~r/\w*:\/\/(?\w+):(?[a-zA-Z0-9-*#!%^&$_]*)?@(?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])):(?\d+)\/(?\w+)/ + ~r/\w*:\/\/(?\w+):(?[a-zA-Z0-9-*#!%^&$_]*)?@(?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])):(?\d+)\/(?[a-zA-Z0-9_-]*)/ |> Regex.named_captures(database_url) |> Keyword.new(fn {k, v} -> {String.to_atom(k), v} end) |> Keyword.put(:url, database_url) @@ -44,4 +57,24 @@ defmodule Explorer.Repo.ConfigHelper do end end) end + + def network_path do + path = System.get_env("NETWORK_PATH", "/") + + path_from_env(path) + end + + def api_path do + path = System.get_env("API_PATH", "/") + + path_from_env(path) + end + + defp path_from_env(path_env_var) do + if String.ends_with?(path_env_var, "/") do + path_env_var + else + path_env_var <> "/" + end + end end diff --git a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex index 781a63c17116..9cb35cb02f1b 100644 --- a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex +++ b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex @@ -3,6 +3,8 @@ defmodule Explorer.SmartContract.CompilerVersion do Adapter for fetching compiler versions from https://solc-bin.ethereum.org/bin/list.json. """ + alias Explorer.SmartContract.RustVerifierInterface + @unsupported_solc_versions ~w(0.1.1 0.1.2) @unsupported_vyper_versions ~w(v0.2.9 v0.2.10) @@ -18,32 +20,40 @@ defmodule Explorer.SmartContract.CompilerVersion do end defp fetch_solc_versions do - headers = [{"Content-Type", "application/json"}] + if RustVerifierInterface.enabled?() do + RustVerifierInterface.get_versions_list() + else + headers = [{"Content-Type", "application/json"}] - case HTTPoison.get(source_url(:solc), headers) do - {:ok, %{status_code: 200, body: body}} -> - {:ok, format_data(body, :solc)} + case HTTPoison.get(source_url(:solc), headers) do + {:ok, %{status_code: 200, body: body}} -> + {:ok, format_data(body, :solc)} - {:ok, %{status_code: _status_code, body: body}} -> - {:error, decode_json(body)["error"]} + {:ok, %{status_code: _status_code, body: body}} -> + {:error, decode_json(body)["error"]} - {:error, %{reason: reason}} -> - {:error, reason} + {:error, %{reason: reason}} -> + {:error, reason} + end end end defp fetch_vyper_versions do - headers = [{"Content-Type", "application/json"}] + if RustVerifierInterface.enabled?() do + RustVerifierInterface.vyper_get_versions_list() + else + headers = [{"Content-Type", "application/json"}] - case HTTPoison.get(source_url(:vyper), headers) do - {:ok, %{status_code: 200, body: body}} -> - {:ok, format_data(body, :vyper)} + case HTTPoison.get(source_url(:vyper), headers) do + {:ok, %{status_code: 200, body: body}} -> + {:ok, format_data(body, :vyper)} - {:ok, %{status_code: _status_code, body: body}} -> - {:error, decode_json(body)["error"]} + {:ok, %{status_code: _status_code, body: body}} -> + {:error, decode_json(body)["error"]} - {:error, %{reason: reason}} -> - {:error, reason} + {:error, %{reason: reason}} -> + {:error, reason} + end end end diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index 59738b146d1b..89a58f0cc83d 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -81,4 +81,24 @@ defmodule Explorer.SmartContract.Helper do |> HTML.safe_to_string() |> String.trim() end + + def sol_file?(filename) do + case List.last(String.split(String.downcase(filename), ".")) do + "sol" -> + true + + _ -> + false + end + end + + def json_file?(filename) do + case List.last(String.split(String.downcase(filename), ".")) do + "json" -> + true + + _ -> + false + end + end end diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index b9f4410a1b56..0d0c8d5b2dce 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -228,11 +228,7 @@ defmodule Explorer.SmartContract.Reader do [] _ -> - abi_with_method_id = get_abi_with_method_id(abi) - - abi_with_method_id - |> Enum.filter(&Helper.queriable_method?(&1)) - |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false)) + read_only_functions_from_abi(abi, contract_address_hash) end end @@ -244,13 +240,7 @@ defmodule Explorer.SmartContract.Reader do [] _ -> - implementation_abi_with_method_id = get_abi_with_method_id(implementation_abi) - - implementation_abi_with_method_id - |> Enum.filter(&Helper.queriable_method?(&1)) - |> Enum.map( - &fetch_current_value_from_blockchain(&1, implementation_abi_with_method_id, contract_address_hash, false) - ) + read_only_functions_from_abi(implementation_abi, contract_address_hash) end end @@ -266,10 +256,7 @@ defmodule Explorer.SmartContract.Reader do [] _ -> - implementation_abi_with_method_id = get_abi_with_method_id(implementation_abi) - - implementation_abi_with_method_id - |> Enum.filter(&Helper.read_with_wallet_method?(&1)) + read_functions_required_wallet_from_abi(implementation_abi) end end @@ -288,13 +275,29 @@ defmodule Explorer.SmartContract.Reader do [] _ -> - abi_with_method_id = get_abi_with_method_id(abi) - - abi_with_method_id - |> Enum.filter(&Helper.read_with_wallet_method?(&1)) + read_functions_required_wallet_from_abi(abi) end end + def read_only_functions_from_abi([_ | _] = abi, contract_address_hash) do + abi_with_method_id = get_abi_with_method_id(abi) + + abi_with_method_id + |> Enum.filter(&Helper.queriable_method?(&1)) + |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false)) + end + + def read_only_functions_from_abi(_, _), do: [] + + def read_functions_required_wallet_from_abi([_ | _] = abi) do + abi_with_method_id = get_abi_with_method_id(abi) + + abi_with_method_id + |> Enum.filter(&Helper.read_with_wallet_method?(&1)) + end + + def read_functions_required_wallet_from_abi(_), do: [] + def get_abi_with_method_id(abi) do abi |> Enum.map(fn method -> @@ -365,14 +368,16 @@ defmodule Explorer.SmartContract.Reader do @doc """ Method performs query of read functions of a smart contract. `type` could be :proxy or :reqular - if ethereumJSONRPC will return some errors it will represented as map + `from` is a address of a function caller """ - @spec query_function_with_names(Hash.t(), %{method_id: String.t(), args: [term()] | nil}, atom()) :: %{ - :names => [any()], - :output => [%{}] - } - def query_function_with_names(contract_address_hash, %{method_id: method_id, args: args}, type) do - outputs = query_function(contract_address_hash, %{method_id: method_id, args: args}, type, true) + @spec query_function_with_names( + Hash.t(), + %{method_id: String.t(), args: [term()] | nil}, + atom(), + String.t() + ) :: %{:names => [any()], :output => [%{}]} + def query_function_with_names(contract_address_hash, %{method_id: method_id, args: args}, type, from) do + outputs = query_function(contract_address_hash, %{method_id: method_id, args: args}, type, from, true) names = parse_names_from_abi(get_abi(contract_address_hash, type), method_id) %{output: outputs, names: names} end @@ -382,15 +387,17 @@ defmodule Explorer.SmartContract.Reader do `type` could be :proxy or :reqular `from` is a address of a function caller """ - @spec query_function_with_names( + @spec query_function_with_names_custom_abi( Hash.t(), %{method_id: String.t(), args: [term()] | nil}, - atom(), - String.t() + String.t(), + [%{}] ) :: %{:names => [any()], :output => [%{}]} - def query_function_with_names(contract_address_hash, %{method_id: method_id, args: args}, type, from) do - outputs = query_function(contract_address_hash, %{method_id: method_id, args: args}, type, from, true) - names = parse_names_from_abi(get_abi(contract_address_hash, type), method_id) + def query_function_with_names_custom_abi(contract_address_hash, %{method_id: method_id, args: args}, from, custom_abi) do + outputs = + query_function_with_custom_abi(contract_address_hash, %{method_id: method_id, args: args}, from, true, custom_abi) + + names = parse_names_from_abi(custom_abi, method_id) %{output: outputs, names: names} end @@ -432,7 +439,77 @@ defmodule Explorer.SmartContract.Reader do query_contract_and_link_outputs(contract_address_hash, args, from, abi, outputs, method_id, leave_error_as_map) end - defp proccess_abi(nil, _method_id), do: nil + @spec query_function_with_custom_abi( + String.t(), + %{method_id: String.t(), args: nil}, + String.t() | nil, + true | false, + [%{}] + ) :: [%{}] + def query_function_with_custom_abi( + contract_address_hash, + %{method_id: method_id, args: nil}, + from, + leave_error_as_map, + custom_abi + ) do + query_function_with_custom_abi( + contract_address_hash, + %{method_id: method_id, args: []}, + from, + leave_error_as_map, + custom_abi + ) + end + + @spec query_function_with_custom_abi( + Hash.t(), + %{method_id: String.t(), args: [term()]}, + String.t() | nil, + true | false, + [%{}] + ) :: [ + %{} + ] + def query_function_with_custom_abi( + contract_address_hash, + %{method_id: method_id, args: args}, + from, + leave_error_as_map, + custom_abi + ) do + query_function_with_custom_abi_inner(contract_address_hash, method_id, args, from, leave_error_as_map, custom_abi) + end + + @spec query_function_with_custom_abi_inner(Hash.t(), String.t(), [term()], String.t() | nil, true | false, [%{}]) :: [ + %{} + ] + defp query_function_with_custom_abi_inner( + contract_address_hash, + method_id, + args, + from, + leave_error_as_map, + custom_abi + ) do + parsed_abi = + custom_abi + |> ABI.parse_specification() + + %{outputs: outputs, method_id: method_id} = proccess_abi(parsed_abi, method_id) + + query_contract_and_link_outputs( + contract_address_hash, + args, + from, + custom_abi, + outputs, + method_id, + leave_error_as_map + ) + end + + defp proccess_abi([], _method_id), do: nil defp proccess_abi(abi, method_id) do function_object = find_function_by_method(abi, method_id) @@ -449,15 +526,12 @@ defmodule Explorer.SmartContract.Reader do end defp get_abi(contract_address_hash, type) do - abi = - contract_address_hash - |> Chain.address_hash_to_smart_contract() - |> Map.get(:abi) + contract = Chain.address_hash_to_smart_contract(contract_address_hash) if type == :proxy do - Chain.get_implementation_abi_from_proxy(contract_address_hash, abi) + Chain.get_implementation_abi_from_proxy(contract) else - abi + contract.abi end end diff --git a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex new file mode 100644 index 000000000000..34a2eab02dcf --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex @@ -0,0 +1,149 @@ +defmodule Explorer.SmartContract.RustVerifierInterface do + @moduledoc """ + Adapter for contracts verification with https://github.com/blockscout/blockscout-rs/blob/main/smart-contract-verifier + """ + alias Explorer.Utility.RustService + alias HTTPoison.Response + require Logger + + @post_timeout :infinity + @request_error_msg "Error while sending request to verification microservice" + + def verify_multi_part( + %{ + "creation_bytecode" => _, + "deployed_bytecode" => _, + "compiler_version" => _, + "sources" => _, + "evm_version" => _, + "optimization_runs" => _, + "contract_libraries" => _ + } = body + ) do + http_post_request(multiple_files_verification_url(), body) + end + + def verify_standard_json_input( + %{ + "creation_bytecode" => _, + "deployed_bytecode" => _, + "compiler_version" => _, + "input" => _ + } = body + ) do + http_post_request(standard_json_input_verification_url(), body) + end + + def vyper_verify_multipart( + %{ + "creation_bytecode" => _, + "deployed_bytecode" => _, + "compiler_version" => _, + "sources" => _ + } = body + ) do + http_post_request(vyper_multiple_files_verification_url(), body) + end + + def http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(normalize_creation_bytecode(body)), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: 200}} -> + proccess_verifier_response(body) + + {:ok, %Response{body: body, status_code: _}} -> + proccess_verifier_response(body) + + {:error, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to verification microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + def http_get_request(url) do + case HTTPoison.get(url) do + {:ok, %Response{body: body, status_code: 200}} -> + proccess_verifier_response(body) + + {:ok, %Response{body: body, status_code: _}} -> + {:error, body} + + {:error, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to verification microservice url: #{url}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + def get_versions_list do + http_get_request(versions_list_url()) + end + + def vyper_get_versions_list do + http_get_request(vyper_versions_list_url()) + end + + def proccess_verifier_response(body) when is_binary(body) do + case Jason.decode(body) do + {:ok, decoded} -> + proccess_verifier_response(decoded) + + _ -> + {:error, body} + end + end + + def proccess_verifier_response(%{"status" => zero, "result" => result}) when zero in ["0", 0] do + {:ok, result} + end + + def proccess_verifier_response(%{"status" => one, "message" => error}) when one in ["1", 1] do + {:error, error} + end + + def proccess_verifier_response(%{"versions" => versions}), do: {:ok, versions} + + def proccess_verifier_response(other), do: {:error, other} + + def normalize_creation_bytecode(%{"creation_bytecode" => ""} = map), do: Map.replace(map, "creation_bytecode", nil) + + def normalize_creation_bytecode(map), do: map + + def multiple_files_verification_url, do: "#{base_api_url()}" <> "/solidity/verify/multiple-files" + + def vyper_multiple_files_verification_url, do: "#{base_api_url()}" <> "/vyper/verify/multiple-files" + + def standard_json_input_verification_url, do: "#{base_api_url()}" <> "/solidity/verify/standard-json" + + def versions_list_url, do: "#{base_api_url()}" <> "/solidity/versions" + + def vyper_versions_list_url, do: "#{base_api_url()}" <> "/vyper/versions" + + def base_api_url, do: "#{base_url()}" <> "/api/v1" + + def base_url do + RustService.base_url(__MODULE__) + end + + def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] +end diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex index f184eedefa00..0ff49cb4806e 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex @@ -28,6 +28,32 @@ defmodule Explorer.SmartContract.Solidity.Publisher do params_with_external_libaries = add_external_libraries(params, external_libraries) case Verifier.evaluate_authenticity(address_hash, params_with_external_libaries) do + { + :ok, + %{ + "abi" => abi_string, + "compiler_version" => _, + "constructor_arguments" => _, + "contract_libraries" => contract_libraries, + "contract_name" => contract_name, + "evm_version" => _, + "file_name" => file_name, + "optimization" => _, + "optimization_runs" => _, + "sources" => sources + } = result_params + } -> + %{^file_name => contract_source_code} = sources + + prepared_params = + result_params + |> Map.put("contract_source_code", contract_source_code) + |> Map.put("external_libraries", contract_libraries) + |> Map.put("name", contract_name) + |> cast_compiler_settings(false) + + publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string || "null")) + {:ok, %{abi: abi, constructor_arguments: constructor_arguments}} -> params_with_constructor_arguments = Map.put(params_with_external_libaries, "constructor_arguments", constructor_arguments) @@ -50,6 +76,21 @@ defmodule Explorer.SmartContract.Solidity.Publisher do def publish_with_standard_json_input(%{"address_hash" => address_hash} = params, json_input) do case Verifier.evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do + {:ok, + %{ + "abi" => _, + "compiler_version" => _, + "constructor_arguments" => _, + "contract_libraries" => _, + "contract_name" => _, + "evm_version" => _, + "file_name" => _, + "optimization" => _, + "optimization_runs" => _, + "sources" => _ + } = result_params} -> + proccess_rust_verifier_response(result_params, address_hash, true) + {:ok, %{abi: abi, constructor_arguments: constructor_arguments}, additional_params} -> params_with_constructor_arguments = params @@ -73,6 +114,80 @@ defmodule Explorer.SmartContract.Solidity.Publisher do end end + def publish_with_multi_part_files(%{"address_hash" => address_hash} = params, external_libraries, files) do + params_with_external_libaries = add_external_libraries(params, external_libraries) + + case Verifier.evaluate_authenticity_via_multi_part_files(address_hash, params_with_external_libaries, files) do + {:ok, + %{ + "abi" => _, + "compiler_version" => _, + "constructor_arguments" => _, + "contract_libraries" => _, + "contract_name" => _, + "evm_version" => _, + "file_name" => _, + "optimization" => _, + "optimization_runs" => _, + "sources" => _ + } = result_params} -> + proccess_rust_verifier_response(result_params, address_hash) + + {:error, error} -> + {:error, unverified_smart_contract(address_hash, params, error, nil, true)} + + _ -> + {:error, unverified_smart_contract(address_hash, params, "Failed to verify", nil, true)} + end + end + + def proccess_rust_verifier_response( + %{ + "abi" => abi_string, + "compiler_version" => _, + "constructor_arguments" => _, + "contract_libraries" => contract_libraries, + "contract_name" => contract_name, + "evm_version" => _, + "file_name" => file_name, + "optimization" => _, + "optimization_runs" => _, + "sources" => sources + } = result_params, + address_hash, + is_standard_json? \\ false + ) do + secondary_sources = + for {file, source} <- sources, + file != file_name, + do: %{"file_name" => file, "contract_source_code" => source, "address_hash" => address_hash} + + %{^file_name => contract_source_code} = sources + + prepared_params = + result_params + |> Map.put("contract_source_code", contract_source_code) + |> Map.put("external_libraries", contract_libraries) + |> Map.put("name", contract_name) + |> Map.put("file_path", file_name) + |> Map.put("secondary_sources", secondary_sources) + |> cast_compiler_settings(is_standard_json?) + + publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string)) + end + + def cast_compiler_settings(params, false), do: Map.put(params, "compiler_settings", nil) + + def cast_compiler_settings(params, true) do + case Jason.decode(params["compiler_settings"]) do + {:ok, map} -> + Map.put(params, "compiler_settings", map) + + _ -> + Map.put(params, "compiler_settings", nil) + end + end + def publish_smart_contract(address_hash, params, abi) do attrs = address_hash |> attributes(params, abi) @@ -117,6 +232,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do defp attributes(address_hash, params, abi \\ %{}) do constructor_arguments = params["constructor_arguments"] + compiler_settings = params["compiler_settings"] clean_constructor_arguments = if constructor_arguments != nil && constructor_arguments != "" do @@ -125,6 +241,13 @@ defmodule Explorer.SmartContract.Solidity.Publisher do nil end + clean_compiler_settings = + if compiler_settings in ["", nil, %{}] do + nil + else + compiler_settings + end + prepared_external_libraries = prepare_external_libraies(params["external_libraries"]) compiler_version = CompilerVersion.get_strict_compiler_version(:solc, params["compiler_version"]) @@ -145,7 +268,9 @@ defmodule Explorer.SmartContract.Solidity.Publisher do verified_via_sourcify: params["verified_via_sourcify"], partially_verified: params["partially_verified"], is_vyper_contract: false, - autodetect_constructor_args: params["autodetect_constructor_args"] + autodetect_constructor_args: params["autodetect_constructor_args"], + is_yul: params["is_yul"] || false, + compiler_settings: clean_compiler_settings } end @@ -160,7 +285,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do defp add_external_libraries(params, external_libraries) do clean_external_libraries = - Enum.reduce(1..10, %{}, fn number, acc -> + Enum.reduce(1..Application.get_env(:block_scout_web, :verification_max_libraries), %{}, fn number, acc -> address_key = "library#{number}_address" name_key = "library#{number}_name" diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex index 893d5a7934b4..dce4ac6a36b0 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex @@ -9,7 +9,7 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do alias Explorer.Chain.SmartContract.VerificationStatus alias Explorer.SmartContract.Solidity.Publisher - def perform({address_hash, params, external_libraries, conn}) do + def perform({"flattened", %{"address_hash" => address_hash} = params, external_libraries, conn}) do result = case Publisher.publish(address_hash, params, external_libraries) do {:ok, _contract} = result -> @@ -22,7 +22,21 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand) end - def perform({%{"address_hash" => address_hash} = params, json_input, uid}) when is_binary(uid) do + def perform({"multipart", %{"address_hash" => address_hash} = params, files_map, external_libraries, conn}) + when is_map(files_map) do + result = + case Publisher.publish_with_multi_part_files(params, external_libraries, files_map) do + {:ok, _contract} = result -> + result + + {:error, changeset} -> + {:error, changeset} + end + + EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand) + end + + def perform({"json_api", %{"address_hash" => address_hash} = params, json_input, uid}) when is_binary(uid) do VerificationStatus.insert_status(uid, :pending, address_hash) case Publisher.publish_with_standard_json_input(params, json_input) do @@ -34,7 +48,7 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do end end - def perform({%{"address_hash" => address_hash} = params, json_input, conn}) do + def perform({"json_web", %{"address_hash" => address_hash} = params, json_input, conn}) do result = case Publisher.publish_with_standard_json_input(params, json_input) do {:ok, _contract} = result -> diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex index 836f65d38ead..eb849469ac58 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex @@ -10,6 +10,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do alias ABI.{FunctionSelector, TypeDecoder} alias Explorer.Chain + alias Explorer.SmartContract.RustVerifierInterface alias Explorer.SmartContract.Solidity.CodeCompiler require Logger @@ -23,27 +24,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do def evaluate_authenticity(address_hash, params) do try do - latest_evm_version = List.last(CodeCompiler.allowed_evm_versions()) - evm_version = Map.get(params, "evm_version", latest_evm_version) - - all_versions = [evm_version | previous_evm_versions(evm_version)] - - all_versions_extra = all_versions ++ [evm_version] - - Enum.reduce_while(all_versions_extra, false, fn version, acc -> - case acc do - {:ok, _} = result -> - {:cont, result} - - {:error, error} - when error in [:name, :no_creation_data, :deployed_bytecode, :compiler_version, :constructor_arguments] -> - {:halt, acc} - - _ -> - cur_params = Map.put(params, "evm_version", version) - {:cont, verify(address_hash, cur_params)} - end - end) + evaluate_authenticity_inner(RustVerifierInterface.enabled?(), address_hash, params) rescue exception -> Logger.error(fn -> @@ -55,9 +36,77 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end end + defp evaluate_authenticity_inner(true, address_hash, params) do + deployed_bytecode = Chain.smart_contract_bytecode(address_hash) + + creation_tx_input = + case Chain.smart_contract_creation_tx_bytecode(address_hash) do + %{init: init, created_contract_code: _created_contract_code} -> + init + + _ -> + "" + end + + params + |> Map.put("creation_bytecode", creation_tx_input) + |> Map.put("deployed_bytecode", deployed_bytecode) + |> Map.put("sources", %{ + "#{params["name"]}.#{smart_contract_source_file_extension(parse_boolean(params["is_yul"]))}" => + params["contract_source_code"] + }) + |> Map.put("contract_libraries", params["external_libraries"]) + |> Map.put("optimization_runs", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) + |> RustVerifierInterface.verify_multi_part() + end + + defp evaluate_authenticity_inner(false, address_hash, params) do + latest_evm_version = List.last(CodeCompiler.allowed_evm_versions()) + evm_version = Map.get(params, "evm_version", latest_evm_version) + + all_versions = [evm_version | previous_evm_versions(evm_version)] + + all_versions_extra = all_versions ++ [evm_version] + + Enum.reduce_while(all_versions_extra, false, fn version, acc -> + case acc do + {:ok, _} = result -> + {:cont, result} + + {:error, error} + when error in [:name, :no_creation_data, :deployed_bytecode, :compiler_version, :constructor_arguments] -> + {:halt, acc} + + _ -> + cur_params = Map.put(params, "evm_version", version) + {:cont, verify(address_hash, cur_params)} + end + end) + end + + defp smart_contract_source_file_extension(true), do: "yul" + defp smart_contract_source_file_extension(_), do: "sol" + + defp prepare_optimization_runs(false_, _) when false_ in [false, "false"], do: nil + + defp prepare_optimization_runs(true_, runs) when true_ in [true, "true"] do + case Integer.parse(runs) do + {runs_integer, ""} -> + runs_integer + + _ -> + nil + end + end + def evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do try do - verify(address_hash, params, json_input) + evaluate_authenticity_via_standard_json_input_inner( + RustVerifierInterface.enabled?(), + address_hash, + params, + json_input + ) rescue exception -> Logger.error(fn -> @@ -69,6 +118,50 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end end + def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do + deployed_bytecode = Chain.smart_contract_bytecode(address_hash) + + creation_tx_input = + case Chain.smart_contract_creation_tx_bytecode(address_hash) do + %{init: init, created_contract_code: _created_contract_code} -> + init + + _ -> + "" + end + + params + |> Map.put("creation_bytecode", creation_tx_input) + |> Map.put("deployed_bytecode", deployed_bytecode) + |> Map.put("input", json_input) + |> RustVerifierInterface.verify_standard_json_input() + end + + def evaluate_authenticity_via_standard_json_input_inner(false, address_hash, params, json_input) do + verify(address_hash, params, json_input) + end + + def evaluate_authenticity_via_multi_part_files(address_hash, params, files) do + deployed_bytecode = Chain.smart_contract_bytecode(address_hash) + + creation_tx_input = + case Chain.smart_contract_creation_tx_bytecode(address_hash) do + %{init: init, created_contract_code: _created_contract_code} -> + init + + _ -> + "" + end + + params + |> Map.put("creation_bytecode", creation_tx_input) + |> Map.put("deployed_bytecode", deployed_bytecode) + |> Map.put("sources", files) + |> Map.put("contract_libraries", params["external_libraries"]) + |> Map.put("optimization_runs", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) + |> RustVerifierInterface.verify_multi_part() + end + defp verify(address_hash, params, json_input) do name = Map.get(params, "name", "") compiler_version = Map.fetch!(params, "compiler_version") @@ -112,6 +205,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do |> Map.put("file_path", file_path) |> Map.put("name", contract_name) |> Map.put("secondary_sources", secondary_sources) + |> Map.put("compiler_settings", map_json_input["settings"]) {:halt, {:ok, verified_data, additional_params}} @@ -438,9 +532,11 @@ defmodule Explorer.SmartContract.Solidity.Verifier do Enum.any?(abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end) end - defp parse_boolean("true"), do: true - defp parse_boolean("false"), do: false + def parse_boolean("true"), do: true + def parse_boolean("false"), do: false + + def parse_boolean(true), do: true + def parse_boolean(false), do: false - defp parse_boolean(true), do: true - defp parse_boolean(false), do: false + def parse_boolean(_), do: false end diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex index 258f0ea888b4..0b9f0ec17a12 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex @@ -10,6 +10,31 @@ defmodule Explorer.SmartContract.Vyper.Publisher do def publish(address_hash, params) do case Verifier.evaluate_authenticity(address_hash, params) do + { + :ok, + %{ + "abi" => abi_string, + "compiler_version" => _, + "constructor_arguments" => _, + "contract_libraries" => contract_libraries, + "contract_name" => contract_name, + "evm_version" => _, + "file_name" => file_name, + "optimization" => _, + "optimization_runs" => _, + "sources" => sources + } = result_params + } -> + %{^file_name => contract_source_code} = sources + + prepared_params = + result_params + |> Map.put("contract_source_code", contract_source_code) + |> Map.put("external_libraries", contract_libraries) + |> Map.put("name", contract_name) + + publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string)) + {:ok, %{abi: abi}} -> publish_smart_contract(address_hash, params, abi) diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex index 1595223a0f6f..48f3385bb729 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex @@ -7,9 +7,11 @@ defmodule Explorer.SmartContract.Vyper.Verifier do against the existing Creation Address Bytecode, if it matches the contract is then Verified. """ + require Logger alias Explorer.Chain alias Explorer.SmartContract.Vyper.CodeCompiler + alias Explorer.SmartContract.RustVerifierInterface def evaluate_authenticity(_, %{"name" => ""}), do: {:error, :name} @@ -17,6 +19,39 @@ defmodule Explorer.SmartContract.Vyper.Verifier do do: {:error, :contract_source_code} def evaluate_authenticity(address_hash, params) do + try do + evaluate_authenticity_inner(RustVerifierInterface.enabled?(), address_hash, params) + rescue + exception -> + Logger.error(fn -> + [ + "Error while verifying smart-contract address: #{address_hash}, params: #{inspect(params, limit: :infinity, printable_limit: :infinity)}: ", + Exception.format(:error, exception) + ] + end) + end + end + + defp evaluate_authenticity_inner(true, address_hash, params) do + deployed_bytecode = Chain.smart_contract_bytecode(address_hash) + + creation_tx_input = + case Chain.smart_contract_creation_tx_bytecode(address_hash) do + %{init: init, created_contract_code: _created_contract_code} -> + init + + _ -> + "" + end + + params + |> Map.put("creation_bytecode", creation_tx_input) + |> Map.put("deployed_bytecode", deployed_bytecode) + |> Map.put("sources", %{"#{params["name"]}.vy" => params["contract_source_code"]}) + |> RustVerifierInterface.vyper_verify_multipart() + end + + defp evaluate_authenticity_inner(false, address_hash, params) do verify(address_hash, params) end diff --git a/apps/explorer/lib/explorer/smart_contract/writer.ex b/apps/explorer/lib/explorer/smart_contract/writer.ex index ba231a655128..2d6c7484bec2 100644 --- a/apps/explorer/lib/explorer/smart_contract/writer.ex +++ b/apps/explorer/lib/explorer/smart_contract/writer.ex @@ -43,8 +43,10 @@ defmodule Explorer.SmartContract.Writer do (Helper.payable?(function) || Helper.nonpayable?(function)) end - defp filter_write_functions(abi) do + def filter_write_functions(abi) when is_list(abi) do abi |> Enum.filter(&write_function?(&1)) end + + def filter_write_functions(_), do: [] end diff --git a/apps/explorer/lib/explorer/tags/address_tag.ex b/apps/explorer/lib/explorer/tags/address_tag.ex new file mode 100644 index 000000000000..617423560881 --- /dev/null +++ b/apps/explorer/lib/explorer/tags/address_tag.ex @@ -0,0 +1,98 @@ +defmodule Explorer.Tags.AddressTag do + @moduledoc """ + Represents a Tag object. + """ + + use Explorer.Schema + + import Ecto.Changeset + + import Ecto.Query, + only: [ + from: 2 + ] + + alias Explorer.Chain.Address + alias Explorer.Repo + alias Explorer.Tags.{AddressTag, AddressToTag} + + @typedoc """ + * `:id` - id of Tag + * `:label` - Tag's label + * `:label` - Label's display name + """ + @type t :: %AddressTag{ + label: String.t() + } + + schema "address_tags" do + field(:label, :string) + field(:display_name, :string) + has_many(:addresses, Address, foreign_key: :hash) + has_many(:tag_id, AddressToTag, foreign_key: :id) + + timestamps() + end + + @required_attrs ~w(label display_name)a + + @doc false + def changeset(struct, params \\ %{}) do + struct + |> cast(params, @required_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:label, name: :address_tags_label_index) + end + + def set_tag(name, display_name) do + tag = get_tag(name) + + if tag do + tag + |> AddressTag.changeset(%{display_name: display_name}) + |> Repo.update() + else + %AddressTag{} + |> AddressTag.changeset(%{label: name, display_name: display_name}) + |> Repo.insert() + end + end + + def get_tag_id(nil), do: nil + + def get_tag_id(label) do + query = + from( + tag in AddressTag, + where: tag.label == ^label, + select: tag.id + ) + + query + |> Repo.one() + end + + def get_tag(nil), do: nil + + def get_tag(label) do + query = + from( + tag in AddressTag, + where: tag.label == ^label + ) + + query + |> Repo.one() + end + + def get_all_tags do + query = + from( + tag in AddressTag, + select: tag + ) + + query + |> Repo.all() + end +end diff --git a/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex b/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex new file mode 100644 index 000000000000..3256f3f3c257 --- /dev/null +++ b/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex @@ -0,0 +1,198 @@ +defmodule Explorer.Tags.AddressTag.Cataloger do + @moduledoc """ + Actualizes address tags. + """ + + use GenServer + + alias Explorer.EnvVarTranslator + alias Explorer.Tags.{AddressTag, AddressToTag} + alias Explorer.Validator.MetadataRetriever + alias Poison.Parser + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl GenServer + def init(args) do + send(self(), :fetch_tags) + + {:ok, args} + end + + @impl GenServer + def handle_info(:fetch_tags, state) do + # set tag for every chainlink oracle + create_chainlink_oracle_tag() + + create_new_tags() + + send(self(), :bind_addresses) + + {:noreply, state} + end + + def handle_info(:bind_addresses, state) do + # set validator tag + set_validator_tag() + + # set amb bridge mediators tag + set_amb_mediators_tag() + + # set omni bridge tag + set_omni_tag() + + # set L2 tag + set_l2_tag() + + all_tags = AddressTag.get_all_tags() + + all_tags + |> Enum.each(fn %{label: tag_name} -> + if tag_name !== "validator" && tag_name !== "amb bridge mediators" && tag_name !== "omni bridge" && + tag_name !== "l2" && !String.contains?(tag_name, "chainlink") do + env_var_name = "CUSTOM_CONTRACT_ADDRESSES_#{tag_name_to_env_var_part(tag_name)}" + set_tag_for_env_var_multiple_addresses(env_var_name, tag_name) + end + end) + + {:noreply, state} + end + + defp tag_name_to_env_var_part(tag_name) do + tag_name + |> String.upcase() + |> String.replace(" ", "_") + |> String.replace(".", "_") + end + + def create_chainlink_oracle_tag do + chainlink_oracles_config = Application.get_env(:block_scout_web, :chainlink_oracles) + + if chainlink_oracles_config do + chainlink_oracles_config + |> Parser.parse!(%{keys: :atoms!}) + |> Enum.each(fn %{:name => name, :address => address} -> + chainlink_tag_name = "chainlink oracle #{String.downcase(name)}" + AddressTag.set_tag(chainlink_tag_name, chainlink_tag_name) + tag_id = AddressTag.get_tag_id(chainlink_tag_name) + AddressToTag.set_tag_to_addresses(tag_id, [address]) + end) + end + end + + defp set_tag_for_multiple_env_var_addresses(env_vars, tag) do + addresses = + env_vars + |> Enum.map(fn env_var -> + env_var + |> System.get_env("") + |> String.downcase() + end) + + tag_id = AddressTag.get_tag_id(tag) + AddressToTag.set_tag_to_addresses(tag_id, addresses) + end + + defp set_tag_for_multiple_env_var_array_addresses(env_vars, tag) do + addresses = + env_vars + |> Enum.reduce([], fn env_var, acc -> + env_var + |> System.get_env("") + |> String.split(",") + |> Enum.reduce(acc, fn env_var, acc_inner -> + addr = + env_var + |> String.downcase() + + [addr | acc_inner] + end) + end) + + tag_id = AddressTag.get_tag_id(tag) + AddressToTag.set_tag_to_addresses(tag_id, addresses) + end + + def create_new_tags do + tags = EnvVarTranslator.map_array_env_var_to_list(:new_tags) + + tags + |> Enum.each(fn %{tag: tag_name, title: tag_display_name} -> + AddressTag.set_tag(tag_name, tag_display_name) + end) + end + + defp set_tag_for_env_var_multiple_addresses(env_var, tag) do + addresses = env_var_string_array_to_list(env_var) + + tag_id = AddressTag.get_tag_id(tag) + AddressToTag.set_tag_to_addresses(tag_id, addresses) + end + + defp env_var_string_array_to_list(env_var_array_string) do + env_var = + env_var_array_string + |> System.get_env(nil) + + if env_var do + env_var + |> String.split(",") + |> Enum.map(fn env_var_array_string_item -> + env_var_array_string_item + |> String.downcase() + end) + else + [] + end + end + + defp set_validator_tag do + validators = MetadataRetriever.fetch_validators_list() + tag_id = AddressTag.get_tag_id("validator") + AddressToTag.set_tag_to_addresses(tag_id, validators) + end + + defp set_amb_mediators_tag do + set_tag_for_multiple_env_var_array_addresses( + ["AMB_BRIDGE_MEDIATORS", "CUSTOM_CONTRACT_ADDRESSES_AMB_BRIDGE_MEDIATORS"], + "amb bridge mediators" + ) + end + + defp set_omni_tag do + set_tag_for_multiple_env_var_addresses( + ["ETH_OMNI_BRIDGE_MEDIATOR", "BSC_OMNI_BRIDGE_MEDIATOR", "POA_OMNI_BRIDGE_MEDIATOR"], + "omni bridge" + ) + end + + defp set_l2_tag do + set_tag_for_multiple_env_var_addresses(["CUSTOM_CONTRACT_ADDRESSES_AOX"], "l2") + end + + def set_chainlink_oracle_tag do + chainlink_oracles = chainlink_oracles_list() + + tag_id = AddressTag.get_tag_id("chainlink oracle") + AddressToTag.set_tag_to_addresses(tag_id, chainlink_oracles) + end + + defp chainlink_oracles_list do + chainlink_oracles_config = Application.get_env(:block_scout_web, :chainlink_oracles) + + if chainlink_oracles_config do + try do + chainlink_oracles_config + |> Parser.parse!(%{keys: :atoms!}) + |> Enum.map(fn %{:name => _name, :address => address} -> address end) + rescue + _ -> + [] + end + else + [] + end + end +end diff --git a/apps/explorer/lib/explorer/tags/address_to_tag.ex b/apps/explorer/lib/explorer/tags/address_to_tag.ex new file mode 100644 index 000000000000..26468cf29abb --- /dev/null +++ b/apps/explorer/lib/explorer/tags/address_to_tag.ex @@ -0,0 +1,145 @@ +defmodule Explorer.Tags.AddressToTag do + @moduledoc """ + Represents ann Address to Tag relation. + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Address, Hash} + alias Explorer.Tags.{AddressTag, AddressToTag} + + # Notation.import_types(BlockScoutWeb.Schema.Types) + + @typedoc """ + * `:tag_id` - id of Tag + * `:address_hash` - hash of Address + """ + @type t :: %AddressToTag{ + tag_id: Decimal.t(), + address_hash: Hash.Address.t() + } + + schema "address_to_tags" do + belongs_to( + :tag, + AddressTag, + foreign_key: :tag_id, + references: :id, + type: :integer + ) + + belongs_to( + :address, + Address, + foreign_key: :address_hash, + references: :hash, + type: Hash.Address + ) + + timestamps() + end + + @required_attrs ~w(address_hash tag_id)a + + @doc false + def changeset(struct, params \\ %{}) do + struct + |> cast(params, @required_attrs) + |> unique_constraint([:address_hash, :tag_id], name: :address_to_tags_address_hash_tag_id_index) + end + + defp get_address_hashes_mapped_to_tag(nil), do: nil + + defp get_address_hashes_mapped_to_tag(tag_id) do + query = + from( + att in AddressToTag, + where: att.tag_id == ^tag_id, + select: att.address_hash + ) + + query + |> Repo.all() + end + + def set_tag_to_addresses(tag_id, address_hash_string_list) do + current_address_hashes = get_address_hashes_mapped_to_tag(tag_id) + + if current_address_hashes do + current_address_hashes_strings = + current_address_hashes + |> Enum.map(fn address_hash -> + "0x" <> Base.encode16(address_hash.bytes, case: :lower) + end) + + current_address_hashes_strings_tuples = MapSet.new(current_address_hashes_strings) + new_address_hashes_strings_tuples = MapSet.new(address_hash_string_list) + + all_tuples = MapSet.union(current_address_hashes_strings_tuples, new_address_hashes_strings_tuples) + + addresses_to_delete = + all_tuples + |> MapSet.difference(new_address_hashes_strings_tuples) + |> MapSet.to_list() + + addresses_to_add = + all_tuples + |> MapSet.difference(current_address_hashes_strings_tuples) + |> MapSet.to_list() + + changeset_to_add_list = + addresses_to_add + |> Enum.map(fn address_hash_string -> + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_address_exists(address_hash) do + %{ + tag_id: tag_id, + address_hash: address_hash, + inserted_at: DateTime.utc_now(), + updated_at: DateTime.utc_now() + } + else + _ -> + nil + end + end) + |> Enum.filter(&(!is_nil(&1))) + + if Enum.count(addresses_to_delete) > 0 do + delete_query_base = + from( + att in AddressToTag, + where: att.tag_id == ^tag_id + ) + + delete_query = + delete_query_base + |> where_addresses(addresses_to_delete) + + Repo.delete_all(delete_query) + end + + Repo.insert_all(AddressToTag, changeset_to_add_list, + on_conflict: :nothing, + conflict_target: [:address_hash, :tag_id] + ) + end + end + + defp where_addresses(query, addresses_to_delete) do + addresses_to_delete + |> Enum.reduce(query, fn address_hash_string, acc -> + case Chain.string_to_address_hash(address_hash_string) do + {:ok, address_hash} -> + acc + |> where(address_hash: ^address_hash) + + _ -> + acc + end + end) + end +end diff --git a/apps/explorer/lib/explorer/third_party_integrations/airtable.ex b/apps/explorer/lib/explorer/third_party_integrations/airtable.ex new file mode 100644 index 000000000000..a2aba9c6640c --- /dev/null +++ b/apps/explorer/lib/explorer/third_party_integrations/airtable.ex @@ -0,0 +1,56 @@ +defmodule Explorer.ThirdPartyIntegrations.AirTable do + @moduledoc """ + Module is responsible for submitting requests for public tags to AirTable + """ + require Logger + + alias Ecto.Changeset + alias Explorer.Account.PublicTagsRequest + alias Explorer.Repo + alias HTTPoison.Response + + def submit({:ok, %PublicTagsRequest{} = new_request} = input) do + if Mix.env() == :test do + new_request + |> PublicTagsRequest.changeset(%{request_id: "123"}) + |> Repo.account_repo().update() + + input + else + api_key = Application.get_env(:explorer, __MODULE__)[:api_key] + headers = [{"Authorization", "Bearer #{api_key}"}, {"Content-Type", "application/json"}] + url = Application.get_env(:explorer, __MODULE__)[:table_url] + + body = %{ + "typecast" => true, + "records" => [%{"fields" => PublicTagsRequest.to_map(new_request)}] + } + + request = HTTPoison.post(url, Jason.encode!(body), headers, []) + + case request do + {:ok, %Response{body: body, status_code: 200}} -> + request_id = Enum.at(Jason.decode!(body)["records"], 0)["fields"]["request_id"] + + new_request + |> PublicTagsRequest.changeset(%{request_id: request_id}) + |> Repo.account_repo().update() + + input + + error -> + Logger.error(fn -> ["Error while submitting AirTable entry", inspect(error)] end) + + {:error, + %{ + (%PublicTagsRequest{} + |> PublicTagsRequest.changeset_without_constraints(PublicTagsRequest.to_map(new_request)) + |> Changeset.add_error(:full_name, "AirTable error. Please try again later")) + | action: :insert + }} + end + end + end + + def submit(error), do: error +end diff --git a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex index a157ae9dfeb5..5ec6cee8ccd6 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex @@ -4,6 +4,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do """ use Tesla + alias Explorer.SmartContract.{Helper, RustVerifierInterface} alias HTTPoison.{Error, Response} alias Tesla.Multipart @@ -11,7 +12,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do @failed_verification_message "Unsuccessful Sourcify verification" def check_by_address(address_hash_string) do - chain_id = config(:chain_id) + chain_id = config(__MODULE__, :chain_id) params = [addresses: address_hash_string, chainIds: chain_id] http_get_request(check_by_address_url(), params) end @@ -27,7 +28,15 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end def verify(address_hash_string, files) do - chain_id = config(:chain_id) + if RustVerifierInterface.enabled?() do + verify_via_rust_microservice(address_hash_string, files) + else + verify_via_sourcify_server(address_hash_string, files) + end + end + + def verify_via_sourcify_server(address_hash_string, files) do + chain_id = config(__MODULE__, :chain_id) multipart_text_params = Multipart.new() @@ -51,6 +60,44 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do http_post_request(verify_url(), multipart_body) end + # sobelow_skip ["Traversal.FileModule"] + def verify_via_rust_microservice(address_hash_string, files) do + chain_id = config(__MODULE__, :chain_id) + + body_params = + Map.new() + |> Map.put("chain", chain_id) + |> Map.put("address", address_hash_string) + + files_body = + files + |> Enum.reduce(Map.new(), fn file, acc -> + if file do + {:ok, file_content} = File.read(file.path) + + file_content = + if Helper.json_file?(file.filename) do + file_content + |> Jason.decode!() + |> Jason.encode!() + else + file_content + end + + acc + |> Map.put(file.filename, file_content) + else + acc + end + end) + + body = + body_params + |> Map.put("files", files_body) + + http_post_request_rust_microservice(verify_url_rust_microservice(), body) + end + def http_get_request(url, params) do request = HTTPoison.get(url, [], params: params) @@ -90,6 +137,18 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end end + def http_post_request_rust_microservice(url, body) do + request = HTTPoison.post(url, Jason.encode!(body), [{"Content-Type", "application/json"}], recv_timeout: :infinity) + + case request do + {:ok, %Response{body: body, status_code: 200}} -> + process_sourcify_response(url, body) + + _ -> + {:error, "Unexpected response from Sourcify verify method"} + end + end + defp process_sourcify_response(url, body) do cond do url =~ "check-by-addresses" -> @@ -113,9 +172,17 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do body_json = decode_json(body) case body_json do + # Success status from native Sourcify server %{"result" => [%{"status" => "perfect"}]} -> {:ok, body_json} + # Success status code from Rust microservice + %{"status" => "0"} -> + {:ok, body_json} + + %{"status" => "1", "message" => message} -> + {:error, message} + %{"result" => [%{"status" => unknown_status}]} -> {:error, unknown_status} @@ -245,6 +312,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do |> Map.put("optimization_runs", Map.get(optimizer, "runs")) |> Map.put("external_libraries", Map.get(settings, "libraries")) |> Map.put("verified_via_sourcify", true) + |> Map.put("compiler_settings", settings) %{ "params_to_publish" => params, @@ -283,31 +351,35 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do _ -> data end - defp config(key) do + defp config(module, key) do :explorer - |> Application.get_env(__MODULE__) + |> Application.get_env(module) |> Keyword.get(key) end defp base_server_url do - config(:server_url) + config(__MODULE__, :server_url) end defp verify_url do "#{base_server_url()}" <> "/verify" end + defp verify_url_rust_microservice do + "#{RustVerifierInterface.base_api_url()}" <> "/sourcify/verify" + end + defp check_by_address_url do "#{base_server_url()}" <> "/check-by-addresses" end defp get_metadata_url do - chain_id = config(:chain_id) + chain_id = config(__MODULE__, :chain_id) "#{base_server_url()}" <> "/files/" <> chain_id end defp get_metadata_any_url do - chain_id = config(:chain_id) + chain_id = config(__MODULE__, :chain_id) "#{base_server_url()}" <> "/files/any/" <> chain_id end diff --git a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex index aa10d73c493b..daf6c4310985 100644 --- a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex @@ -60,6 +60,9 @@ defmodule Explorer.Token.InstanceMetadataRetriever do @no_uri_error "no uri" @vm_execution_error "VM execution error" + # https://eips.ethereum.org/EIPS/eip-1155#metadata + @erc1155_token_id_placeholder "{id}" + def fetch_metadata(unquote(@cryptokitties_address_hash), token_id) do %{"tokenURI" => {:ok, ["https://api.cryptokitties.co/kitties/#{token_id}"]}} |> fetch_json() @@ -75,11 +78,13 @@ defmodule Explorer.Token.InstanceMetadataRetriever do |> fetch_json() if res == {:ok, %{error: @vm_execution_error}} do + hex_normalized_token_id = token_id |> Integer.to_string(16) |> String.downcase() |> String.pad_leading(64, "0") + contract_functions_uri = %{@uri => [token_id]} contract_address_hash |> query_contract(contract_functions_uri, @abi_uri) - |> fetch_json() + |> fetch_json(hex_normalized_token_id) else res end @@ -89,46 +94,58 @@ defmodule Explorer.Token.InstanceMetadataRetriever do Reader.query_contract(contract_address_hash, abi, contract_functions, false) end - def fetch_json(uri) when uri in [%{@token_uri => {:ok, [""]}}, %{@uri => {:ok, [""]}}] do + def fetch_json(uri, hex_token_id \\ nil) + + def fetch_json(uri, _hex_token_id) when uri in [%{@token_uri => {:ok, [""]}}, %{@uri => {:ok, [""]}}] do {:ok, %{error: @no_uri_error}} end - def fetch_json(uri) + def fetch_json(uri, _hex_token_id) when uri in [ %{@token_uri => {:error, "(-32015) VM execution error."}}, - %{@uri => {:error, "(-32015) VM execution error."}} + %{@uri => {:error, "(-32015) VM execution error."}}, + %{@token_uri => {:error, "(-32000) execution reverted"}}, + %{@uri => {:error, "(-32000) execution reverted"}} ] do {:ok, %{error: @vm_execution_error}} end - def fetch_json(%{@token_uri => {:error, "(-32015) VM execution error." <> _}}) do + def fetch_json(%{@token_uri => {:error, "(-32015) VM execution error." <> _}}, _hex_token_id) do + {:ok, %{error: @vm_execution_error}} + end + + def fetch_json(%{@uri => {:error, "(-32015) VM execution error." <> _}}, _hex_token_id) do + {:ok, %{error: @vm_execution_error}} + end + + def fetch_json(%{@token_uri => {:error, "(-32000) execution reverted" <> _}}, _hex_token_id) do {:ok, %{error: @vm_execution_error}} end - def fetch_json(%{@uri => {:error, "(-32015) VM execution error." <> _}}) do + def fetch_json(%{@uri => {:error, "(-32000) execution reverted" <> _}}, _hex_token_id) do {:ok, %{error: @vm_execution_error}} end - def fetch_json(%{@token_uri => {:ok, ["http://" <> _ = token_uri]}}) do - fetch_metadata(token_uri) + def fetch_json(%{@token_uri => {:ok, ["http://" <> _ = token_uri]}}, hex_token_id) do + fetch_metadata_inner(token_uri, hex_token_id) end - def fetch_json(%{@uri => {:ok, ["http://" <> _ = token_uri]}}) do - fetch_metadata(token_uri) + def fetch_json(%{@uri => {:ok, ["http://" <> _ = token_uri]}}, hex_token_id) do + fetch_metadata_inner(token_uri, hex_token_id) end - def fetch_json(%{@token_uri => {:ok, ["https://" <> _ = token_uri]}}) do - fetch_metadata(token_uri) + def fetch_json(%{@token_uri => {:ok, ["https://" <> _ = token_uri]}}, hex_token_id) do + fetch_metadata_inner(token_uri, hex_token_id) end - def fetch_json(%{@uri => {:ok, ["https://" <> _ = token_uri]}}) do - fetch_metadata(token_uri) + def fetch_json(%{@uri => {:ok, ["https://" <> _ = token_uri]}}, hex_token_id) do + fetch_metadata_inner(token_uri, hex_token_id) end - def fetch_json(%{@token_uri => {:ok, ["data:application/json," <> json]}}) do + def fetch_json(%{@token_uri => {:ok, ["data:application/json," <> json]}}, hex_token_id) do decoded_json = URI.decode(json) - fetch_json(%{@token_uri => {:ok, [decoded_json]}}) + fetch_json(%{@token_uri => {:ok, [decoded_json]}}, hex_token_id) rescue e -> Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"], @@ -138,10 +155,10 @@ defmodule Explorer.Token.InstanceMetadataRetriever do {:error, json} end - def fetch_json(%{@uri => {:ok, ["data:application/json," <> json]}}) do + def fetch_json(%{@uri => {:ok, ["data:application/json," <> json]}}, hex_token_id) do decoded_json = URI.decode(json) - fetch_json(%{@token_uri => {:ok, [decoded_json]}}) + fetch_json(%{@uri => {:ok, [decoded_json]}}, hex_token_id) rescue e -> Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"], @@ -151,30 +168,64 @@ defmodule Explorer.Token.InstanceMetadataRetriever do {:error, json} end - def fetch_json(%{@token_uri => {:ok, ["ipfs://ipfs/" <> ipfs_uid]}}) do + def fetch_json(%{@token_uri => {:ok, ["data:application/json;base64," <> base64_encoded_json]}}, hex_token_id) do + case Base.url_decode64(base64_encoded_json) do + {:ok, base64_decoded} -> + fetch_json(%{@token_uri => {:ok, [base64_decoded]}}, hex_token_id) + + _ -> + {:error, base64_encoded_json} + end + rescue + e -> + Logger.debug(["Unknown metadata format base64 #{inspect(base64_encoded_json)}. error #{inspect(e)}"], + fetcher: :token_instances + ) + + {:error, base64_encoded_json} + end + + def fetch_json(%{@uri => {:ok, ["data:application/json;base64," <> base64_encoded_json]}}, hex_token_id) do + case Base.url_decode64(base64_encoded_json) do + {:ok, base64_decoded} -> + fetch_json(%{@uri => {:ok, [base64_decoded]}}, hex_token_id) + + _ -> + {:error, base64_encoded_json} + end + rescue + e -> + Logger.debug(["Unknown metadata format base64 #{inspect(base64_encoded_json)}. error #{inspect(e)}"], + fetcher: :token_instances + ) + + {:error, base64_encoded_json} + end + + def fetch_json(%{@token_uri => {:ok, ["ipfs://ipfs/" <> ipfs_uid]}}, hex_token_id) do ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid - fetch_metadata(ipfs_url) + fetch_metadata_inner(ipfs_url, hex_token_id) end - def fetch_json(%{@uri => {:ok, ["ipfs://ipfs/" <> ipfs_uid]}}) do + def fetch_json(%{@uri => {:ok, ["ipfs://ipfs/" <> ipfs_uid]}}, hex_token_id) do ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid - fetch_metadata(ipfs_url) + fetch_metadata_inner(ipfs_url, hex_token_id) end - def fetch_json(%{@token_uri => {:ok, ["ipfs://" <> ipfs_uid]}}) do + def fetch_json(%{@token_uri => {:ok, ["ipfs://" <> ipfs_uid]}}, hex_token_id) do ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid - fetch_metadata(ipfs_url) + fetch_metadata_inner(ipfs_url, hex_token_id) end - def fetch_json(%{@uri => {:ok, ["ipfs://" <> ipfs_uid]}}) do + def fetch_json(%{@uri => {:ok, ["ipfs://" <> ipfs_uid]}}, hex_token_id) do ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid - fetch_metadata(ipfs_url) + fetch_metadata_inner(ipfs_url, hex_token_id) end - def fetch_json(%{@token_uri => {:ok, [json]}}) do + def fetch_json(%{@token_uri => {:ok, [json]}}, hex_token_id) do {:ok, json} = decode_json(json) - check_type(json) + check_type(json, hex_token_id) rescue e -> Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"], @@ -184,10 +235,10 @@ defmodule Explorer.Token.InstanceMetadataRetriever do {:error, json} end - def fetch_json(%{@uri => {:ok, [json]}}) do + def fetch_json(%{@uri => {:ok, [json]}}, hex_token_id) do {:ok, json} = decode_json(json) - check_type(json) + check_type(json, hex_token_id) rescue e -> Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"], @@ -197,29 +248,31 @@ defmodule Explorer.Token.InstanceMetadataRetriever do {:error, json} end - def fetch_json(result) do + def fetch_json(result, _hex_token_id) do Logger.debug(["Unknown metadata format #{inspect(result)}."], fetcher: :token_instances) {:error, result} end - defp fetch_metadata(uri) do - case HTTPoison.get(uri) do + defp fetch_metadata_inner(uri, hex_token_id) do + prepared_uri = substitute_token_id_to_token_uri(uri, hex_token_id) + + case HTTPoison.get(prepared_uri) do {:ok, %Response{body: body, status_code: 200, headers: headers}} -> if Enum.member?(headers, {"Content-Type", "image/png"}) do - json = %{"image" => uri} + json = %{"image" => prepared_uri} - check_type(json) + check_type(json, nil) else {:ok, json} = decode_json(body) - check_type(json) + check_type(json, hex_token_id) end {:ok, %Response{body: body, status_code: 301}} -> {:ok, json} = decode_json(body) - check_type(json) + check_type(json, hex_token_id) {:ok, %Response{body: body}} -> {:error, body} @@ -246,11 +299,33 @@ defmodule Explorer.Token.InstanceMetadataRetriever do end end - defp check_type(json) when is_map(json) do + defp check_type(json, nil) when is_map(json) do {:ok, %{metadata: json}} end - defp check_type(_) do + defp check_type(json, hex_token_id) when is_map(json) do + metadata = + case json + |> Jason.encode!() + |> String.replace(@erc1155_token_id_placeholder, hex_token_id) + |> Jason.decode() do + {:ok, map} -> + map + + _ -> + json + end + + {:ok, %{metadata: metadata}} + end + + defp check_type(_, _) do {:error, :wrong_metadata_type} end + + defp substitute_token_id_to_token_uri(token_uri, empty_token_id) when empty_token_id in [nil, ""], do: token_uri + + defp substitute_token_id_to_token_uri(token_uri, hex_token_id) do + String.replace(token_uri, @erc1155_token_id_placeholder, hex_token_id) + end end diff --git a/apps/explorer/lib/explorer/token/instance_owner_reader.ex b/apps/explorer/lib/explorer/token/instance_owner_reader.ex new file mode 100644 index 000000000000..52b74778649b --- /dev/null +++ b/apps/explorer/lib/explorer/token/instance_owner_reader.ex @@ -0,0 +1,76 @@ +defmodule Explorer.Token.InstanceOwnerReader do + @moduledoc """ + Reads Token Instance owner using Smart Contract function from the blockchain. + """ + + require Logger + + alias Explorer.SmartContract.Reader + + @owner_function_signature "6352211e" + + @owner_function_abi [ + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [ + %{ + "type" => "address", + "name" => "owner" + } + ], + "name" => "ownerOf", + "inputs" => [ + %{ + "type" => "uint256", + "name" => "tokenId" + } + ] + } + ] + + @spec get_owner_of([%{token_contract_address_hash: String.t(), token_id: integer}]) :: [ + {:ok, String.t()} | {:error, String.t()} + ] + def get_owner_of(instance_owner_requests) do + instance_owner_requests + |> Enum.map(&format_owner_request/1) + |> Reader.query_contracts(@owner_function_abi) + |> Enum.zip(instance_owner_requests) + |> Enum.reduce([], fn {result, request}, acc -> + case format_owner_result(result, request) do + {:ok, ok_result} -> + [ok_result] ++ acc + + {:error, error_message} -> + Logger.error( + "Failed to get owner of token #{request.token_contract_address_hash}, token_id #{request.token_id}, reason: #{error_message}" + ) + + acc + end + end) + end + + defp format_owner_request(%{token_contract_address_hash: token_contract_address_hash, token_id: token_id}) do + %{ + contract_address: token_contract_address_hash, + method_id: @owner_function_signature, + args: [token_id] + } + end + + defp format_owner_result({:ok, [owner]}, request) do + {:ok, + %{ + token_contract_address_hash: request.token_contract_address_hash, + token_id: request.token_id, + owner: owner + }} + end + + defp format_owner_result({:error, error_message}, _request) do + {:error, error_message} + end +end diff --git a/apps/explorer/lib/explorer/token/metadata_retriever.ex b/apps/explorer/lib/explorer/token/metadata_retriever.ex index 304b9330b68d..29b744d708a0 100644 --- a/apps/explorer/lib/explorer/token/metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/metadata_retriever.ex @@ -342,7 +342,10 @@ defmodule Explorer.Token.MetadataRetriever do defp handle_large_string(nil), do: nil defp handle_large_string(string), do: handle_large_string(string, byte_size(string)) - defp handle_large_string(string, size) when size > 255, do: binary_part(string, 0, 255) + + defp handle_large_string(string, size) when size > 255, + do: string |> binary_part(0, 255) |> String.chunk(:valid) |> List.first() + defp handle_large_string(string, _size), do: string defp remove_null_bytes(string) do diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex new file mode 100644 index 000000000000..5794e524ccfc --- /dev/null +++ b/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex @@ -0,0 +1,65 @@ +defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater do + @moduledoc """ + Collects processed block numbers from token id migration workers + and updates last_processed_block_number according to them. + Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc. + """ + use GenServer + + alias Explorer.Utility.TokenTransferTokenIdMigratorProgress + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_) do + last_processed_block_number = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number() + + {:ok, %{last_processed_block_number: last_processed_block_number, processed_ranges: []}} + end + + def add_range(from, to) do + GenServer.cast(__MODULE__, {:add_range, from..to}) + end + + @impl true + def handle_cast({:add_range, range}, %{processed_ranges: processed_ranges} = state) do + ranges = + [range | processed_ranges] + |> Enum.sort_by(& &1.last, &>=/2) + |> normalize_ranges() + + {new_last_number, new_ranges} = maybe_update_last_processed_number(state.last_processed_block_number, ranges) + + {:noreply, %{last_processed_block_number: new_last_number, processed_ranges: new_ranges}} + end + + defp normalize_ranges(ranges) do + %{prev_range: prev, result: result} = + Enum.reduce(ranges, %{prev_range: nil, result: []}, fn range, %{prev_range: prev_range, result: result} -> + case {prev_range, range} do + {nil, _} -> + %{prev_range: range, result: result} + + {%{last: l1} = r1, %{first: f2} = r2} when l1 - 1 > f2 -> + %{prev_range: r2, result: [r1 | result]} + + {%{first: f1}, %{last: l2}} -> + %{prev_range: f1..l2, result: result} + end + end) + + Enum.reverse([prev | result]) + end + + # since ranges are normalized, we need to check only the first range to determine the new last_processed_number + defp maybe_update_last_processed_number(current_last, [from..to | rest] = ranges) when current_last - 1 <= from do + case TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(to) do + {:ok, _} -> {to, rest} + _ -> {current_last, ranges} + end + end + + defp maybe_update_last_processed_number(current_last, ranges), do: {current_last, ranges} +end diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex new file mode 100644 index 000000000000..2121158fee35 --- /dev/null +++ b/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex @@ -0,0 +1,70 @@ +defmodule Explorer.TokenTransferTokenIdMigration.Supervisor do + @moduledoc """ + Supervises parts of token id migration process. + + Migration process algorithm: + + Defining the bounds of migration (by the first and the last block number of TokenTransfer). + Supervisor starts the workers in amount equal to 'TOKEN_ID_MIGRATION_CONCURRENCY' env value (defaults to 1) + and the 'LowestBlockNumberUpdater'. + + Each worker goes through the token transfers by batches ('TOKEN_ID_MIGRATION_BATCH_SIZE', defaults to 500) + and updates the token_ids to be equal of [token_id] for transfers that has any token_id. + Worker goes from the newest blocks to latest. + After worker is done with current batch, it sends the information about processed batch to 'LowestBlockNumberUpdater' + and takes the next by defining its bounds based on amount of all workers. + + For example, if batch size is 10, we have 5 workers and 100 items to be processed, + the distribution will be like this: + 1 worker - 99..90, 49..40 + 2 worker - 89..80, 39..30 + 3 worker - 79..70, 29..20 + 4 worker - 69..60, 19..10 + 5 worker - 59..50, 9..0 + + 'LowestBlockNumberUpdater' keeps the information about the last processed block number + (which is stored in the 'token_transfer_token_id_migrator_progress' db entity) + and block ranges that has already been processed by the workers but couldn't be committed + to last processed block number yet (because of the possible gap between the current last block + and upper bound of the last processed batch). Uncommitted block numbers are stored in normalize ranges. + When there is no gap between the last processed block number and the upper bound of the upper range, + 'LowestBlockNumberUpdater' updates the last processed block number in db and drops this range from its state. + + This supervisor won't start if the migration is completed + (last processed block number in db == 'TOKEN_ID_MIGRATION_FIRST_BLOCK' (defaults to 0)). + """ + use Supervisor + + alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker} + alias Explorer.Utility.TokenTransferTokenIdMigratorProgress + + @default_first_block 0 + @default_workers_count 1 + + def start_link(_) do + Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_) do + first_block = Application.get_env(:explorer, :token_id_migration)[:first_block] || @default_first_block + last_block = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number() + + if last_block > first_block do + workers_count = Application.get_env(:explorer, :token_id_migration)[:concurrency] || @default_workers_count + + workers = + Enum.map(1..workers_count, fn id -> + Supervisor.child_spec( + {Worker, idx: id, first_block: first_block, last_block: last_block, step: workers_count - 1}, + id: {Worker, id}, + restart: :transient + ) + end) + + Supervisor.init([LowestBlockNumberUpdater | workers], strategy: :one_for_one) + else + :ignore + end + end +end diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex new file mode 100644 index 000000000000..f7916ed0582a --- /dev/null +++ b/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex @@ -0,0 +1,84 @@ +defmodule Explorer.TokenTransferTokenIdMigration.Worker do + @moduledoc """ + Performs the migration of TokenTransfer token_id to token_ids by batches. + Full algorithm is in the 'Explorer.TokenTransferTokenIdMigration.Supervisor' module doc. + """ + use GenServer + + import Ecto.Query + + alias Explorer.Chain.TokenTransfer + alias Explorer.Repo + alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater + + @default_batch_size 500 + @interval 10 + + def start_link(idx: idx, first_block: first, last_block: last, step: step) do + GenServer.start_link(__MODULE__, %{idx: idx, bottom_block: first, last_block: last, step: step}) + end + + @impl true + def init(%{idx: idx, bottom_block: bottom_block, last_block: last_block, step: step}) do + batch_size = Application.get_env(:explorer, :token_id_migration)[:batch_size] || @default_batch_size + range = calculate_new_range(last_block, bottom_block, batch_size, idx - 1) + + schedule_next_update() + + {:ok, %{batch_size: batch_size, bottom_block: bottom_block, step: step, current_range: range}} + end + + @impl true + def handle_info(:update, %{current_range: :out_of_bound} = state) do + {:stop, :normal, state} + end + + @impl true + def handle_info(:update, %{current_range: {lower_bound, upper_bound}} = state) do + case do_update(lower_bound, upper_bound) do + true -> + LowestBlockNumberUpdater.add_range(upper_bound, lower_bound) + new_range = calculate_new_range(lower_bound, state.bottom_block, state.batch_size, state.step) + schedule_next_update() + {:noreply, %{state | current_range: new_range}} + + _ -> + schedule_next_update() + {:noreply, state} + end + end + + defp calculate_new_range(last_processed_block, bottom_block, batch_size, step) do + upper_bound = last_processed_block - step * batch_size - 1 + lower_bound = max(upper_bound - batch_size + 1, bottom_block) + + if upper_bound >= bottom_block do + {lower_bound, upper_bound} + else + :out_of_bound + end + end + + defp do_update(lower_bound, upper_bound) do + token_transfers_batch_query = + from( + tt in TokenTransfer, + where: tt.block_number >= ^lower_bound, + where: tt.block_number <= ^upper_bound + ) + + token_transfers_batch_query + |> Repo.all() + |> Enum.filter(fn %{token_id: token_id} -> not is_nil(token_id) end) + |> Enum.map(fn token_transfer -> + token_transfer + |> TokenTransfer.changeset(%{token_ids: [token_transfer.token_id], token_id: nil}) + |> Repo.update() + end) + |> Enum.all?(&match?({:ok, _}, &1)) + end + + defp schedule_next_update do + Process.send_after(self(), :update, @interval) + end +end diff --git a/apps/explorer/lib/explorer/utility/event_notification.ex b/apps/explorer/lib/explorer/utility/event_notification.ex new file mode 100644 index 000000000000..8ae9dd0a2f5f --- /dev/null +++ b/apps/explorer/lib/explorer/utility/event_notification.ex @@ -0,0 +1,20 @@ +defmodule Explorer.Utility.EventNotification do + @moduledoc """ + An auxiliary schema for sending postgres notifications. + """ + + use Explorer.Schema + + schema "event_notifications" do + field(:data, :string) + end + + @doc false + def changeset(event_notification, params \\ %{}) do + cast(event_notification, params, [:data]) + end + + def new_changeset(data) do + changeset(%__MODULE__{}, %{data: data}) + end +end diff --git a/apps/explorer/lib/explorer/utility/rust_service.ex b/apps/explorer/lib/explorer/utility/rust_service.ex new file mode 100644 index 000000000000..63f949961252 --- /dev/null +++ b/apps/explorer/lib/explorer/utility/rust_service.ex @@ -0,0 +1,15 @@ +defmodule Explorer.Utility.RustService do + @moduledoc """ + Module is responsible for common utils related to rust microservices. + """ + def base_url(module) do + url = Application.get_env(:explorer, module)[:service_url] + + if String.ends_with?(url, "/") do + url + |> String.slice(0..(String.length(url) - 2)) + else + url + end + end +end diff --git a/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex b/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex new file mode 100644 index 000000000000..3a28181016e5 --- /dev/null +++ b/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex @@ -0,0 +1,67 @@ +defmodule Explorer.Utility.TokenTransferTokenIdMigratorProgress do + @moduledoc """ + Module is responsible for keeping the current progress of TokenTransfer token_id migration. + Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc. + """ + use Explorer.Schema + + require Logger + + alias Explorer.Chain.Cache.BlockNumber + alias Explorer.Repo + + schema "token_transfer_token_id_migrator_progress" do + field(:last_processed_block_number, :integer) + + timestamps() + end + + @doc false + def changeset(progress \\ %__MODULE__{}, params) do + cast(progress, params, [:last_processed_block_number]) + end + + def get_current_progress do + Repo.one( + from( + p in __MODULE__, + order_by: [desc: p.updated_at], + limit: 1 + ) + ) + end + + def get_last_processed_block_number do + case get_current_progress() do + nil -> + latest_processed_block_number = BlockNumber.get_max() + 1 + update_last_processed_block_number(latest_processed_block_number) + latest_processed_block_number + + %{last_processed_block_number: block_number} -> + block_number + end + end + + def update_last_processed_block_number(block_number, force \\ false) do + case get_current_progress() do + nil -> + %{last_processed_block_number: block_number} + |> changeset() + |> Repo.insert() + + progress -> + if not force and progress.last_processed_block_number < block_number do + Logger.error( + "TokenTransferTokenIdMigratorProgress new block_number is above the last one. Last: #{progress.last_processed_block_number}, new: #{block_number}" + ) + + {:error, :invalid_block_number} + else + progress + |> changeset(%{last_processed_block_number: block_number}) + |> Repo.update() + end + end + end +end diff --git a/apps/explorer/lib/explorer/validator/metadata_retriever.ex b/apps/explorer/lib/explorer/validator/metadata_retriever.ex index 99fb83db799d..3a5d13eefad6 100644 --- a/apps/explorer/lib/explorer/validator/metadata_retriever.ex +++ b/apps/explorer/lib/explorer/validator/metadata_retriever.ex @@ -15,7 +15,7 @@ defmodule Explorer.Validator.MetadataRetriever do end) end - defp fetch_validators_list do + def fetch_validators_list do # b7ab4db5 = keccak256(getValidators()) case Reader.query_contract( config(:validators_contract_address), diff --git a/apps/explorer/lib/explorer/vault.ex b/apps/explorer/lib/explorer/vault.ex new file mode 100644 index 000000000000..70fc1ed17d7a --- /dev/null +++ b/apps/explorer/lib/explorer/vault.ex @@ -0,0 +1,22 @@ +defmodule Explorer.Vault do + @moduledoc """ + Module responsible for encrypt/decrypt GenServer initialization + """ + use Cloak.Vault, otp_app: :explorer + + @impl GenServer + def init(config) do + config = + Keyword.put(config, :ciphers, + default: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: decode_env!("ACCOUNT_CLOAK_KEY")} + ) + + {:ok, config} + end + + defp decode_env!(var) do + env = if Mix.env() == :test, do: "+fh7IElJfA61+vMMw8rW9SBJFHmhVL1DLpKE22qUJgw=", else: System.get_env(var) + + Base.decode64!(env || "") + end +end diff --git a/apps/explorer/lib/explorer/visualize/sol2uml.ex b/apps/explorer/lib/explorer/visualize/sol2uml.ex new file mode 100644 index 000000000000..b507c63a2e10 --- /dev/null +++ b/apps/explorer/lib/explorer/visualize/sol2uml.ex @@ -0,0 +1,68 @@ +defmodule Explorer.Visualize.Sol2uml do + @moduledoc """ + Adapter for sol2uml visualizer with https://github.com/blockscout/blockscout-rs/blob/main/visualizer + """ + alias Explorer.Utility.RustService + alias HTTPoison.Response + require Logger + + @post_timeout 60_000 + @request_error_msg "Error while sending request to visualizer microservice" + + def visualize_contracts(body) do + http_post_request(visualize_contracts_url(), body) + end + + def http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: 200}} -> + proccess_visualizer_response(body) + + {:ok, %Response{body: body, status_code: status_code}} -> + Logger.error(fn -> ["Invalid status code from visualizer: #{status_code}. body: ", inspect(body)] end) + {:error, "failed to visualize contract"} + + {:error, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to visualizer microservice. url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + def proccess_visualizer_response(body) when is_binary(body) do + case Jason.decode(body) do + {:ok, decoded} -> + proccess_visualizer_response(decoded) + + _ -> + {:error, body} + end + end + + def proccess_visualizer_response(%{"svg" => svg}) do + {:ok, svg} + end + + def proccess_visualizer_response(other), do: {:error, other} + + def visualize_contracts_url, do: "#{base_api_url()}" <> "/solidity:visualize-contracts" + + def base_api_url, do: "#{base_url()}" <> "/api/v1" + + def base_url do + RustService.base_url(__MODULE__) + end + + def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] +end diff --git a/apps/explorer/lib/release_tasks.ex b/apps/explorer/lib/release_tasks.ex index 146e77d6e2c1..1fcdc7fca8c3 100644 --- a/apps/explorer/lib/release_tasks.ex +++ b/apps/explorer/lib/release_tasks.ex @@ -14,7 +14,20 @@ defmodule Explorer.ReleaseTasks do :ecto_sql ] - @repos Application.compile_env(:blockscout, :ecto_repos, [Explorer.Repo]) + @repos Application.compile_env(:blockscout, :ecto_repos, [Explorer.Repo, Explorer.Repo.Account]) + + def create_and_migrate do + start_services() + + create() + run_migrations() + + stop_services() + end + + def create do + Enum.each(@repos, &create_db_for/1) + end def migrate(_argv) do start_services() @@ -43,7 +56,7 @@ defmodule Explorer.ReleaseTasks do IO.puts("Starting repos..") # Switch pool_size to 2 for ecto > 3.0 - Enum.each(@repos, & &1.start_link(pool_size: 1)) + Enum.each(@repos, & &1.start_link(pool_size: 2)) end defp stop_services do @@ -51,6 +64,16 @@ defmodule Explorer.ReleaseTasks do :init.stop() end + defp create_db_for(repo) do + IO.puts("Create #{inspect(repo)} database if it doesn't exist") + + case repo.__adapter__.storage_up(repo.config) do + :ok -> :ok + {:error, :already_up} -> :ok + {:error, term} -> {:error, term} + end + end + defp run_migrations do Enum.each(@repos, &run_migrations_for/1) end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 92400af79749..bc10d9856b4d 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -15,7 +15,7 @@ defmodule Explorer.Mixfile do plt_add_apps: ~w(ex_unit mix)a, ignore_warnings: "../../.dialyzer-ignore" ], - elixir: "~> 1.10", + elixir: "~> 1.13", elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", package: package(), @@ -24,7 +24,8 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "0.0.1" + version: "4.1.8", + xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] ] end @@ -56,14 +57,16 @@ defmodule Explorer.Mixfile do # Type `mix help deps` for examples and options. defp deps do [ - {:bcrypt_elixir, "~> 1.0"}, + {:bamboo, "~> 2.2.0"}, + {:mime, "~> 1.4"}, + {:bcrypt_elixir, "~> 3.0"}, # benchmark optimizations - {:benchee, "~> 0.13.1", only: :test}, + {:benchee, "~> 1.1.0", only: :test}, # CSV output for benchee - {:benchee_csv, "~> 0.8.0", only: :test}, - {:bypass, "~> 1.0", only: :test}, + {:benchee_csv, "~> 1.0.0", only: :test}, + {:bypass, "~> 2.1", only: :test}, {:briefly, "~> 0.4", github: "CargoSense/briefly"}, - {:comeonin, "~> 4.0"}, + {:comeonin, "~> 5.3"}, {:credo, "~> 1.5", only: :test, runtime: false}, # For Absinthe to load data in batches {:dataloader, "~> 1.0.0"}, @@ -83,9 +86,9 @@ defmodule Explorer.Mixfile do {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, # Log errors and application output to separate files {:logger_file_backend, "~> 0.0.10"}, - {:math, "~> 0.3.0"}, + {:math, "~> 0.7.0"}, {:mock, "~> 0.3.0", only: [:test], runtime: false}, - {:mox, "~> 0.4", only: [:test]}, + {:mox, "~> 1.0", only: [:test]}, {:phoenix_html, "== 3.0.4"}, {:poison, "~> 4.0.1"}, {:nimble_csv, "~> 1.1"}, @@ -94,6 +97,7 @@ defmodule Explorer.Mixfile do {:prometheus, "~> 4.0", override: true}, # Prometheus metrics for query duration {:prometheus_ecto, "~> 1.4.3"}, + {:prometheus_ex, git: "https://github.com/lanodan/prometheus.ex", branch: "fix/elixir-1.14", override: true}, # bypass optional dependency {:plug_cowboy, "~> 2.2", only: [:dev, :test]}, {:que, "~> 0.10.1"}, @@ -103,14 +107,16 @@ defmodule Explorer.Mixfile do # `:spandex` integration with Datadog {:spandex_datadog, "~> 1.0"}, # `:spandex` tracing of `:ecto` - {:spandex_ecto, "~> 0.6.2"}, + {:spandex_ecto, "~> 0.7.0"}, # Attach `:prometheus_ecto` to `:ecto` {:telemetry, "~> 0.4.3"}, # `Timex.Duration` for `Explorer.Counters.AverageBlockTime.average_block_time/0` {:timex, "~> 3.7.1"}, {:con_cache, "~> 1.0"}, - {:tesla, "~> 1.3.3"}, - {:cbor, "~> 1.0"} + {:tesla, "~> 1.4.4"}, + {:cbor, "~> 1.0"}, + {:cloak_ecto, "~> 1.2.0"}, + {:redix, "~> 1.1"} ] end @@ -136,7 +142,7 @@ defmodule Explorer.Mixfile do defp package do [ - maintainers: ["POA Networks Ltd."], + maintainers: ["Blockscout"], licenses: ["GPL 3.0"], links: %{"GitHub" => "https://github.com/blockscout/blockscout"} ] diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json index 787f1f877f7f..2b48149e5187 100644 --- a/apps/explorer/package-lock.json +++ b/apps/explorer/package-lock.json @@ -7,41 +7,25 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "solc": "^0.8.0" + "solc": "0.8.16" }, "engines": { "node": "16.x", "npm": "8.x" } }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" }, "node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } }, "node_modules/follow-redirects": { "version": "1.14.8", @@ -62,82 +46,11 @@ } } }, - "node_modules/fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, - "node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -146,25 +59,6 @@ "node": ">= 0.10.0" } }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -173,33 +67,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -209,25 +76,23 @@ } }, "node_modules/solc": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.0.tgz", - "integrity": "sha512-ypgvqYZhb/i5BM6cw9/5QkSlDJm/rLynsbWGP3kz6HeB6oNxPK6UMiB7jMr+tNVbQbBM/8l47vrI3XaDCHShjQ==", + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.16.tgz", + "integrity": "sha512-6oZg7FAhIouj2zYLvoR3Q4fMP/+BGPR7sY7GcrEXKIp+DRd8RmpDEFO1LUBKpClUiaYguNgmthTFmnPl4MeiMQ==", "dependencies": { "command-exists": "^1.2.8", - "commander": "3.0.2", + "commander": "^8.1.0", "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", "js-sha3": "0.8.0", "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { - "solcjs": "solcjs" + "solcjs": "solc.js" }, "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" } }, "node_modules/tmp": { @@ -240,179 +105,54 @@ "engines": { "node": ">=0.6.0" } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } }, "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" }, "commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" }, "follow-redirects": { "version": "1.14.8", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" }, - "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "requires": { - "graceful-fs": "^4.1.9" - } - }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "solc": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.0.tgz", - "integrity": "sha512-ypgvqYZhb/i5BM6cw9/5QkSlDJm/rLynsbWGP3kz6HeB6oNxPK6UMiB7jMr+tNVbQbBM/8l47vrI3XaDCHShjQ==", + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.16.tgz", + "integrity": "sha512-6oZg7FAhIouj2zYLvoR3Q4fMP/+BGPR7sY7GcrEXKIp+DRd8RmpDEFO1LUBKpClUiaYguNgmthTFmnPl4MeiMQ==", "requires": { "command-exists": "^1.2.8", - "commander": "3.0.2", + "commander": "^8.1.0", "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", "js-sha3": "0.8.0", "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", "semver": "^5.5.0", "tmp": "0.0.33" } @@ -424,11 +164,6 @@ "requires": { "os-tmpdir": "~1.0.2" } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } } diff --git a/apps/explorer/package.json b/apps/explorer/package.json index eb8441eed3a1..3c5c00095998 100644 --- a/apps/explorer/package.json +++ b/apps/explorer/package.json @@ -5,7 +5,7 @@ }, "private": true, "name": "blockscout", - "author": "POA Network", + "author": "Blockscout", "license": "GPL-3.0", "engines": { "node": "16.x", @@ -13,6 +13,6 @@ }, "scripts": {}, "dependencies": { - "solc": "0.8.0" + "solc": "0.8.16" } } diff --git a/apps/explorer/priv/account/migrations/20211031164954_create_account_identities.exs b/apps/explorer/priv/account/migrations/20211031164954_create_account_identities.exs new file mode 100644 index 000000000000..fb571247f0b4 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20211031164954_create_account_identities.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.Account.Migrations.CreateAccountIdentities do + use Ecto.Migration + + def change do + create table(:account_identities) do + add(:uid, :string) + + timestamps() + end + + create(unique_index(:account_identities, [:uid])) + end +end diff --git a/apps/explorer/priv/account/migrations/20211105114502_create_account_watchlists.exs b/apps/explorer/priv/account/migrations/20211105114502_create_account_watchlists.exs new file mode 100644 index 000000000000..2e7c93f3cc97 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20211105114502_create_account_watchlists.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Account.Migrations.CreateAccountWatchlists do + use Ecto.Migration + + def change do + create table(:account_watchlists) do + add(:name, :string, default: "default") + add(:identity_id, references(:account_identities, on_delete: :delete_all)) + + timestamps() + end + + create(index(:account_watchlists, [:identity_id])) + end +end diff --git a/apps/explorer/priv/account/migrations/20211105130907_create_account_watchlist_addresses.exs b/apps/explorer/priv/account/migrations/20211105130907_create_account_watchlist_addresses.exs new file mode 100644 index 000000000000..ef51c1850e5e --- /dev/null +++ b/apps/explorer/priv/account/migrations/20211105130907_create_account_watchlist_addresses.exs @@ -0,0 +1,28 @@ +defmodule Explorer.Repo.Account.Migrations.CreateAccountWatchlistAddresses do + use Ecto.Migration + + def change do + create table(:account_watchlist_addresses) do + add(:name, :string) + add(:address_hash, :bytea, null: false) + add(:watchlist_id, references(:account_watchlists, on_delete: :delete_all)) + add(:watch_coin_input, :boolean, default: true) + add(:watch_coin_output, :boolean, default: true) + add(:watch_erc_20_input, :boolean, default: true) + add(:watch_erc_20_output, :boolean, default: true) + add(:watch_erc_721_input, :boolean, default: true) + add(:watch_erc_721_output, :boolean, default: true) + add(:watch_erc_1155_input, :boolean, default: true) + add(:watch_erc_1155_output, :boolean, default: true) + add(:notify_email, :boolean, default: true) + add(:notify_epns, :boolean, default: false) + add(:notify_feed, :boolean, default: true) + add(:notify_inapp, :boolean, default: false) + + timestamps() + end + + create(index(:account_watchlist_addresses, [:watchlist_id])) + create(index(:account_watchlist_addresses, [:address_hash])) + end +end diff --git a/apps/explorer/priv/account/migrations/20211127212336_create_account_watchlist_notifications.exs b/apps/explorer/priv/account/migrations/20211127212336_create_account_watchlist_notifications.exs new file mode 100644 index 000000000000..7d4af566fe46 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20211127212336_create_account_watchlist_notifications.exs @@ -0,0 +1,31 @@ +defmodule Explorer.Repo.Account.Migrations.CreateAccountWatchlistNotifications do + use Ecto.Migration + + def change do + create table(:account_watchlist_notifications) do + add(:watchlist_address_id, references(:account_watchlist_addresses, on_delete: :delete_all)) + + add(:transaction_hash, :bytea) + + add(:from_address_hash, :bytea) + + add(:to_address_hash, :bytea) + + add(:direction, :string) + add(:name, :string) + add(:type, :string) + add(:method, :string) + add(:block_number, :integer) + add(:amount, :decimal) + add(:tx_fee, :decimal) + add(:viewed_at, :utc_datetime_usec) + + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:account_watchlist_notifications, [:watchlist_address_id])) + create(index(:account_watchlist_notifications, [:transaction_hash])) + create(index(:account_watchlist_notifications, [:from_address_hash])) + create(index(:account_watchlist_notifications, [:to_address_hash])) + end +end diff --git a/apps/explorer/priv/account/migrations/20211205220414_add_email_and_name_to_account_identity.exs b/apps/explorer/priv/account/migrations/20211205220414_add_email_and_name_to_account_identity.exs new file mode 100644 index 000000000000..0633e0dbaeac --- /dev/null +++ b/apps/explorer/priv/account/migrations/20211205220414_add_email_and_name_to_account_identity.exs @@ -0,0 +1,10 @@ +defmodule Explorer.Repo.Account.Migrations.AddEmailToAccountIdentity do + use Ecto.Migration + + def change do + alter table(:account_identities) do + add(:email, :string) + add(:name, :string) + end + end +end diff --git a/apps/explorer/priv/account/migrations/20220212222222_create_account_tag_addresses.exs b/apps/explorer/priv/account/migrations/20220212222222_create_account_tag_addresses.exs new file mode 100644 index 000000000000..df0074041ee9 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220212222222_create_account_tag_addresses.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.Account.Migrations.CreateAccountTagAddresses do + use Ecto.Migration + + def change do + create table(:account_tag_addresses) do + add(:name, :string) + add(:identity_id, references(:account_identities, on_delete: :delete_all)) + + add(:address_hash, :bytea, null: false) + + timestamps() + end + + create(index(:account_tag_addresses, [:identity_id])) + create(index(:account_tag_addresses, [:address_hash])) + end +end diff --git a/apps/explorer/priv/account/migrations/20220313133333_create_account_tag_transactions.exs b/apps/explorer/priv/account/migrations/20220313133333_create_account_tag_transactions.exs new file mode 100644 index 000000000000..ba6fa338b984 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220313133333_create_account_tag_transactions.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.Account.Migrations.CreateAccountTagTransactions do + use Ecto.Migration + + def change do + create table(:account_tag_transactions) do + add(:name, :string) + add(:identity_id, references(:account_identities, on_delete: :delete_all)) + + add(:tx_hash, :bytea, null: false) + + timestamps() + end + + create(index(:account_tag_transactions, [:identity_id])) + create(index(:account_tag_transactions, [:tx_hash])) + end +end diff --git a/apps/explorer/priv/account/migrations/20220324213333_add_subject_to_watchlist_notifications.exs b/apps/explorer/priv/account/migrations/20220324213333_add_subject_to_watchlist_notifications.exs new file mode 100644 index 000000000000..6ff8a359ba4d --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220324213333_add_subject_to_watchlist_notifications.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Account.Migrations.AddSubjectToWatchlistNotifications do + use Ecto.Migration + + def change do + alter table(:account_watchlist_notifications) do + add(:subject, :string, null: true) + end + end +end diff --git a/apps/explorer/priv/account/migrations/20220407134152_add_api_keys_and_plans_tables.exs b/apps/explorer/priv/account/migrations/20220407134152_add_api_keys_and_plans_tables.exs new file mode 100644 index 000000000000..11f23ffd6126 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220407134152_add_api_keys_and_plans_tables.exs @@ -0,0 +1,33 @@ +defmodule Explorer.Repo.Account.Migrations.AddApiKeysAndPlansTables do + use Ecto.Migration + + def change do + create table(:account_api_plans, primary_key: false) do + add(:id, :serial, null: false, primary_key: true) + add(:max_req_per_second, :smallint) + add(:name, :string, null: false) + + timestamps() + end + + create(unique_index(:account_api_plans, [:id, :max_req_per_second, :name])) + + execute( + "INSERT INTO account_api_plans (id, max_req_per_second, name, inserted_at, updated_at) VALUES (1, 10, 'Free Plan', NOW(), NOW());" + ) + + create table(:account_api_keys, primary_key: false) do + add(:identity_id, references(:account_identities, column: :id, on_delete: :delete_all), null: false) + add(:name, :string, null: false) + add(:value, :uuid, null: false, primary_key: true) + + timestamps() + end + + alter table(:account_identities) do + add(:plan_id, references(:account_api_plans, column: :id), default: 1) + end + + create(index(:account_api_keys, [:identity_id])) + end +end diff --git a/apps/explorer/priv/account/migrations/20220510094118_add_custom_abis_table.exs b/apps/explorer/priv/account/migrations/20220510094118_add_custom_abis_table.exs new file mode 100644 index 000000000000..a248c79458bf --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220510094118_add_custom_abis_table.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Repo.Account.Migrations.AddCustomAbisTable do + use Ecto.Migration + + def change do + create table(:account_custom_abis, primary_key: false) do + add(:id, :serial, null: false, primary_key: true) + add(:identity_id, references(:account_identities, column: :id, on_delete: :delete_all), null: false) + add(:name, :string, null: false) + add(:address_hash, :bytea, null: false) + add(:abi, :jsonb, null: false) + + timestamps() + end + + create(unique_index(:account_custom_abis, [:identity_id, :address_hash])) + create(index(:account_custom_abis, [:identity_id])) + end +end diff --git a/apps/explorer/priv/account/migrations/20220606194836_add_account_public_tags_requests.exs b/apps/explorer/priv/account/migrations/20220606194836_add_account_public_tags_requests.exs new file mode 100644 index 000000000000..dd0a9896b0ca --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220606194836_add_account_public_tags_requests.exs @@ -0,0 +1,23 @@ +defmodule Explorer.Repo.Account.Migrations.AddAccountPublicTagsRequests do + use Ecto.Migration + + def change do + create table(:account_public_tags_requests) do + add(:identity_id, references(:account_identities)) + add(:full_name, :string) + add(:email, :string) + add(:company, :string) + add(:website, :string) + add(:tags, :string) + add(:addresses, :text) + add(:description, :text) + add(:additional_comment, :string) + add(:request_type, :string) + add(:is_owner, :boolean) + add(:remove_reason, :text) + add(:request_id, :string) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/account/migrations/20220620182600_add_account_identity_fields.exs b/apps/explorer/priv/account/migrations/20220620182600_add_account_identity_fields.exs new file mode 100644 index 000000000000..1c7b13535f3c --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220620182600_add_account_identity_fields.exs @@ -0,0 +1,10 @@ +defmodule Explorer.Repo.Account.Migrations.AddAccountIdentityFields do + use Ecto.Migration + + def change do + alter table("account_identities") do + add(:nickname, :string, null: true) + add(:avatar, :text, null: true) + end + end +end diff --git a/apps/explorer/priv/account/migrations/20220624142547_add_unique_constraints.exs b/apps/explorer/priv/account/migrations/20220624142547_add_unique_constraints.exs new file mode 100644 index 000000000000..53293983d4a6 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220624142547_add_unique_constraints.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Account.Migrations.AddUniqueConstraints do + use Ecto.Migration + + def change do + create(unique_index(:account_tag_addresses, [:identity_id, :address_hash])) + create(unique_index(:account_tag_transactions, [:identity_id, :tx_hash])) + create(unique_index(:account_watchlist_addresses, [:watchlist_id, :address_hash])) + end +end diff --git a/apps/explorer/priv/account/migrations/20220705195240_migrate_public_tags_addresses_to_array.exs b/apps/explorer/priv/account/migrations/20220705195240_migrate_public_tags_addresses_to_array.exs new file mode 100644 index 000000000000..1282e2166c15 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220705195240_migrate_public_tags_addresses_to_array.exs @@ -0,0 +1,34 @@ +defmodule Explorer.Repo.Account.Migrations.MigratePublicTagsAddressesToArray do + use Ecto.Migration + + def change do + alter table(:account_public_tags_requests) do + add(:addresses_duplicate, {:array, :bytea}) + end + + execute(""" + CREATE OR REPLACE FUNCTION convert(text[]) RETURNS bytea[] AS $$ + DECLARE + s bytea[] := ARRAY[]::bytea[]; + x text; + BEGIN + FOREACH x IN ARRAY $1 + LOOP + s := array_append(s, decode(replace(x, '0x', ''), 'hex')); + END LOOP; + RETURN s; + END; + $$ LANGUAGE plpgsql; + """) + + execute(""" + UPDATE account_public_tags_requests set addresses_duplicate = convert(string_to_array(addresses, ';')) + """) + + alter table(:account_public_tags_requests) do + remove(:addresses) + end + + rename(table(:account_public_tags_requests), :addresses_duplicate, to: :addresses) + end +end diff --git a/apps/explorer/priv/account/migrations/20220706114430_encrypt_account_data.exs b/apps/explorer/priv/account/migrations/20220706114430_encrypt_account_data.exs new file mode 100644 index 000000000000..0328025982a5 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220706114430_encrypt_account_data.exs @@ -0,0 +1,60 @@ +defmodule Explorer.Repo.Account.Migrations.EncryptAccountData do + use Ecto.Migration + + def change do + alter table(:account_identities) do + add(:encrypted_uid, :binary) + add(:uid_hash, :binary) + add(:encrypted_email, :binary) + add(:encrypted_name, :binary) + add(:encrypted_nickname, :binary, null: true) + add(:encrypted_avatar, :binary, null: true) + end + + # unused because we dont have personal watchlists, only autogenerated `default` for each identity + # alter table(:account_watchlists) do + # add(:encrypted_name, :binary) + # end + + alter table(:account_custom_abis) do + add(:address_hash_hash, :binary) + add(:encrypted_address_hash, :binary) + add(:encrypted_name, :binary) + end + + alter table(:account_tag_addresses) do + add(:address_hash_hash, :binary) + add(:encrypted_name, :binary) + add(:encrypted_address_hash, :binary) + end + + alter table(:account_tag_transactions) do + add(:tx_hash_hash, :binary) + add(:encrypted_name, :binary) + add(:encrypted_tx_hash, :binary) + end + + alter table(:account_watchlist_addresses) do + add(:address_hash_hash, :binary) + add(:encrypted_name, :binary) + add(:encrypted_address_hash, :binary) + end + + alter table(:account_watchlist_notifications) do + add(:encrypted_name, :binary) + add(:encrypted_subject, :binary, null: true) + add(:encrypted_from_address_hash, :binary) + add(:encrypted_to_address_hash, :binary) + add(:encrypted_transaction_hash, :binary) + add(:subject_hash, :binary, null: true) + add(:from_address_hash_hash, :binary, null: true) + add(:to_address_hash_hash, :binary, null: true) + add(:transaction_hash_hash, :binary, null: true) + end + + alter table(:account_public_tags_requests) do + add(:encrypted_email, :binary) + add(:encrypted_full_name, :binary) + end + end +end diff --git a/apps/explorer/priv/account/migrations/20220706153506_remove_unencrypted_fields.exs b/apps/explorer/priv/account/migrations/20220706153506_remove_unencrypted_fields.exs new file mode 100644 index 000000000000..b887ac9f7912 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220706153506_remove_unencrypted_fields.exs @@ -0,0 +1,79 @@ +defmodule Explorer.Repo.Account.Migrations.RemoveUnencryptedFields do + use Ecto.Migration + + def change do + alter table(:account_identities) do + remove(:uid) + remove(:email) + remove(:name) + remove(:nickname) + remove(:avatar) + end + + rename(table(:account_identities), :encrypted_uid, to: :uid) + rename(table(:account_identities), :encrypted_email, to: :email) + rename(table(:account_identities), :encrypted_name, to: :name) + rename(table(:account_identities), :encrypted_nickname, to: :nickname) + rename(table(:account_identities), :encrypted_avatar, to: :avatar) + + # unused because we dont have personal watchlists, only autogenerated `default` for each identity + # alter table(:account_watchlists) do + # remove(:name) + # end + # rename(table(:account_watchlists), :encrypted_name, to: :name) + + alter table(:account_custom_abis) do + remove(:address_hash) + remove(:name) + end + + rename(table(:account_custom_abis), :encrypted_address_hash, to: :address_hash) + rename(table(:account_custom_abis), :encrypted_name, to: :name) + + alter table(:account_tag_addresses) do + remove(:address_hash) + remove(:name) + end + + rename(table(:account_tag_addresses), :encrypted_address_hash, to: :address_hash) + rename(table(:account_tag_addresses), :encrypted_name, to: :name) + + alter table(:account_tag_transactions) do + remove(:tx_hash) + remove(:name) + end + + rename(table(:account_tag_transactions), :encrypted_tx_hash, to: :tx_hash) + rename(table(:account_tag_transactions), :encrypted_name, to: :name) + + alter table(:account_watchlist_addresses) do + remove(:address_hash) + remove(:name) + end + + rename(table(:account_watchlist_addresses), :encrypted_address_hash, to: :address_hash) + rename(table(:account_watchlist_addresses), :encrypted_name, to: :name) + + alter table(:account_watchlist_notifications) do + remove(:to_address_hash) + remove(:from_address_hash) + remove(:transaction_hash) + remove(:subject) + remove(:name) + end + + rename(table(:account_watchlist_notifications), :encrypted_name, to: :name) + rename(table(:account_watchlist_notifications), :encrypted_subject, to: :subject) + rename(table(:account_watchlist_notifications), :encrypted_from_address_hash, to: :from_address_hash) + rename(table(:account_watchlist_notifications), :encrypted_to_address_hash, to: :to_address_hash) + rename(table(:account_watchlist_notifications), :encrypted_transaction_hash, to: :transaction_hash) + + alter table(:account_public_tags_requests) do + remove(:full_name) + remove(:email) + end + + rename(table(:account_public_tags_requests), :encrypted_full_name, to: :full_name) + rename(table(:account_public_tags_requests), :encrypted_email, to: :email) + end +end diff --git a/apps/explorer/priv/account/migrations/20220706211444_set_new_indexes.exs b/apps/explorer/priv/account/migrations/20220706211444_set_new_indexes.exs new file mode 100644 index 000000000000..26abb92abeb2 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220706211444_set_new_indexes.exs @@ -0,0 +1,43 @@ +defmodule Explorer.Repo.Account.Migrations.SetNewIndexes do + use Ecto.Migration + + def change do + drop_if_exists(unique_index(:account_tag_addresses, [:identity_id, :address_hash])) + drop_if_exists(unique_index(:account_tag_transactions, [:identity_id, :tx_hash])) + drop_if_exists(unique_index(:account_watchlist_addresses, [:watchlist_id, :address_hash])) + drop_if_exists(unique_index(:account_custom_abis, [:identity_id, :address_hash])) + + drop_if_exists(index(:account_watchlist_notifications, [:transaction_hash])) + drop_if_exists(index(:account_watchlist_notifications, [:from_address_hash])) + drop_if_exists(index(:account_watchlist_notifications, [:to_address_hash])) + + drop_if_exists(unique_index(:account_identities, [:uid])) + + drop_if_exists(index(:account_tag_addresses, [:address_hash])) + drop_if_exists(index(:account_tag_transactions, [:tx_hash])) + + drop_if_exists(index(:account_watchlist_addresses, [:address_hash])) + + create(unique_index(:account_tag_addresses, [:identity_id, :address_hash_hash])) + create(unique_index(:account_tag_transactions, [:identity_id, :tx_hash_hash])) + + create( + unique_index(:account_watchlist_addresses, [:watchlist_id, :address_hash_hash], + name: "unique_watchlist_id_address_hash_hash_index" + ) + ) + + create(unique_index(:account_custom_abis, [:identity_id, :address_hash_hash])) + + create(index(:account_watchlist_notifications, [:transaction_hash_hash])) + create(index(:account_watchlist_notifications, [:from_address_hash_hash])) + create(index(:account_watchlist_notifications, [:to_address_hash_hash])) + + create(unique_index(:account_identities, [:uid_hash])) + + create(index(:account_tag_addresses, [:address_hash_hash])) + create(index(:account_tag_transactions, [:tx_hash_hash])) + + create(index(:account_watchlist_addresses, [:address_hash_hash])) + end +end diff --git a/apps/explorer/priv/account/migrations/20220905195203_remove_guardian_tokens.exs b/apps/explorer/priv/account/migrations/20220905195203_remove_guardian_tokens.exs new file mode 100644 index 000000000000..2c76df8099c0 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20220905195203_remove_guardian_tokens.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Account.Migrations.RemoveGuardianTokens do + use Ecto.Migration + + def change do + drop_if_exists(table("guardian_tokens")) + end +end diff --git a/apps/explorer/priv/account/migrations/20221104104635_create_token_transfer_token_id_migrator_progress.exs b/apps/explorer/priv/account/migrations/20221104104635_create_token_transfer_token_id_migrator_progress.exs new file mode 100644 index 000000000000..2fa317af207a --- /dev/null +++ b/apps/explorer/priv/account/migrations/20221104104635_create_token_transfer_token_id_migrator_progress.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Account.Migrations.CreateTokenTransferTokenIdMigratorProgress do + use Ecto.Migration + + def change do + create table(:token_transfer_token_id_migrator_progress) do + add(:last_processed_block_number, :integer) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20210219080523_add_tags.exs b/apps/explorer/priv/repo/migrations/20210219080523_add_tags.exs new file mode 100644 index 000000000000..79e83f808ce6 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20210219080523_add_tags.exs @@ -0,0 +1,24 @@ +defmodule Explorer.Repo.Migrations.AddTags do + use Ecto.Migration + + def change do + create table(:address_tags, primary_key: false) do + add(:id, :serial, null: false) + add(:label, :string, null: false) + + timestamps() + end + + create(unique_index(:address_tags, [:id])) + create(unique_index(:address_tags, [:label])) + + create table(:address_to_tags) do + add(:address_hash, references(:addresses, column: :hash, type: :bytea), null: false) + add(:tag_id, references(:address_tags, column: :id, type: :serial), null: false) + + timestamps() + end + + create(unique_index(:address_to_tags, [:address_hash, :tag_id])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20211210184136_add_display_name_to_address_tag.exs b/apps/explorer/priv/repo/migrations/20211210184136_add_display_name_to_address_tag.exs new file mode 100644 index 000000000000..f07af29e10f9 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20211210184136_add_display_name_to_address_tag.exs @@ -0,0 +1,15 @@ +defmodule Explorer.Repo.Migrations.AddDisplayNameToAddressTag do + use Ecto.Migration + + def up do + alter table(:address_tags) do + add(:display_name, :string, null: true) + end + end + + def down do + alter table(:address_tags) do + remove(:display_name) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs b/apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs new file mode 100644 index 000000000000..5f3839dd4a4c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs @@ -0,0 +1,10 @@ +defmodule Explorer.Repo.Migrations.AddImplementationFields do + use Ecto.Migration + + def change do + alter table(:smart_contracts) do + add(:implementation_address_hash, :bytea, null: true) + add(:implementation_fetched_at, :"timestamp without time zone", null: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220622114402_remove_staking_tables.exs b/apps/explorer/priv/repo/migrations/20220622114402_remove_staking_tables.exs index acfa3895164b..53f9e9516642 100644 --- a/apps/explorer/priv/repo/migrations/20220622114402_remove_staking_tables.exs +++ b/apps/explorer/priv/repo/migrations/20220622114402_remove_staking_tables.exs @@ -2,7 +2,12 @@ defmodule Explorer.Repo.Migrations.RemoveStakingTables do use Ecto.Migration def change do - drop_if_exists(table(:staking_pools)) - drop_if_exists(table(:staking_pools_delegators)) + execute(""" + DROP TABLE staking_pools_delegators CASCADE; + """) + + execute(""" + DROP TABLE staking_pools CASCADE; + """) end end diff --git a/apps/explorer/priv/repo/migrations/20220706101103_address_coin_balances_daily_add_primary_key.exs b/apps/explorer/priv/repo/migrations/20220706101103_address_coin_balances_daily_add_primary_key.exs new file mode 100644 index 000000000000..e13e2f897ade --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220706101103_address_coin_balances_daily_add_primary_key.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.Migrations.AddressCoinBalancesDailyAddPrimaryKey do + use Ecto.Migration + + def change do + drop( + unique_index( + :address_coin_balances_daily, + ~w(address_hash day)a + ) + ) + + alter table(:address_coin_balances_daily) do + modify(:address_hash, :bytea, null: false, primary_key: true) + modify(:day, :date, null: false, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220706102257_address_coin_balances_add_primary_key.exs b/apps/explorer/priv/repo/migrations/20220706102257_address_coin_balances_add_primary_key.exs new file mode 100644 index 000000000000..c3d48b640d3b --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220706102257_address_coin_balances_add_primary_key.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.Migrations.AddressCoinBalancesAddPrimaryKey do + use Ecto.Migration + + def change do + drop( + unique_index( + :address_coin_balances, + ~w(address_hash block_number)a + ) + ) + + alter table(:address_coin_balances) do + modify(:address_hash, :bytea, null: false, primary_key: true) + modify(:block_number, :bigint, null: false, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220706102504_transactions_forks_add_primary_key.exs b/apps/explorer/priv/repo/migrations/20220706102504_transactions_forks_add_primary_key.exs new file mode 100644 index 000000000000..242ee5c250f1 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220706102504_transactions_forks_add_primary_key.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.Migrations.TransactionsForksAddPrimaryKey do + use Ecto.Migration + + def change do + drop( + unique_index( + :transaction_forks, + ~w(uncle_hash index)a + ) + ) + + alter table(:transaction_forks) do + modify(:uncle_hash, :bytea, null: false, primary_key: true) + modify(:index, :integer, null: false, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220706102746_block_rewards_add_primary_key.exs b/apps/explorer/priv/repo/migrations/20220706102746_block_rewards_add_primary_key.exs new file mode 100644 index 000000000000..a908a89367e8 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220706102746_block_rewards_add_primary_key.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Repo.Migrations.BlockRewardsAddPrimaryKey do + use Ecto.Migration + + def change do + drop( + unique_index( + :block_rewards, + ~w(address_hash block_hash address_type)a + ) + ) + + alter table(:block_rewards) do + modify(:address_hash, :bytea, null: false, primary_key: true) + modify(:block_hash, :bytea, null: false, primary_key: true) + modify(:address_type, :string, null: false, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220706105925_emission_rewards_add_primary_key.exs b/apps/explorer/priv/repo/migrations/20220706105925_emission_rewards_add_primary_key.exs new file mode 100644 index 000000000000..877b0f1441c5 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220706105925_emission_rewards_add_primary_key.exs @@ -0,0 +1,16 @@ +defmodule Explorer.Repo.Migrations.EmissionRewardsAddPrimaryKey do + use Ecto.Migration + + def change do + drop( + index( + :emission_rewards, + ~w(block_range)a + ) + ) + + alter table(:emission_rewards) do + modify(:block_range, :int8range, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220706111510_address_names_add_primary_key.exs b/apps/explorer/priv/repo/migrations/20220706111510_address_names_add_primary_key.exs new file mode 100644 index 000000000000..606eded6b58a --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220706111510_address_names_add_primary_key.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddressNamesAddPrimaryKey do + use Ecto.Migration + + def change do + alter table(:address_names) do + add(:id, :serial, null: false, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220804114005_create_event_notifications.exs b/apps/explorer/priv/repo/migrations/20220804114005_create_event_notifications.exs new file mode 100644 index 000000000000..953cfd4472e0 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220804114005_create_event_notifications.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.CreateEventNotifications do + use Ecto.Migration + + def change do + create table(:event_notifications) do + add(:data, :text, null: false) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220902083436_extend_token_name_type.exs b/apps/explorer/priv/repo/migrations/20220902083436_extend_token_name_type.exs new file mode 100644 index 000000000000..fee8460def3b --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220902083436_extend_token_name_type.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.ExtendTokenNameType do + use Ecto.Migration + + def change do + alter table(:tokens) do + modify(:name, :text, null: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs b/apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs new file mode 100644 index 000000000000..e7f30e9cc07f --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.Migrations.CreateIndexTokenTransfersTokenIds do + use Ecto.Migration + + def up do + execute(""" + CREATE INDEX token_transfers_token_ids_index on token_transfers USING GIN ("token_ids") + """) + end + + def down do + execute("DROP INDEX token_transfers_token_ids_index") + end +end diff --git a/apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs b/apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs new file mode 100644 index 000000000000..a91fc7d0fe3d --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs @@ -0,0 +1,8 @@ +defmodule Explorer.Repo.Migrations.CreateIndexesTokenInstancesTokenContractAddressHashTokenId do + use Ecto.Migration + + def change do + create_if_not_exists(index(:token_instances, [:token_contract_address_hash, :token_id])) + create_if_not_exists(index(:token_instances, [:token_id])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20220919105140_add_method_id_index.exs b/apps/explorer/priv/repo/migrations/20220919105140_add_method_id_index.exs new file mode 100644 index 000000000000..95ad38588c15 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220919105140_add_method_id_index.exs @@ -0,0 +1,15 @@ +defmodule Explorer.Repo.Migrations.AddMethodIdIndex do + use Ecto.Migration + + @disable_ddl_transaction true + + def up do + execute(""" + CREATE INDEX CONCURRENTLY IF NOT EXISTS method_id ON public.transactions USING btree (substring(input for 4)); + """) + end + + def down do + execute("DROP INDEX IF EXISTS method_id") + end +end diff --git a/apps/explorer/priv/repo/migrations/20220926122620_extend_token_symbol_type.exs b/apps/explorer/priv/repo/migrations/20220926122620_extend_token_symbol_type.exs new file mode 100644 index 000000000000..e97255a160aa --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220926122620_extend_token_symbol_type.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.ExtendTokenSymbolType do + use Ecto.Migration + + def change do + alter table(:tokens) do + modify(:symbol, :text, null: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20221104104635_create_token_transfer_token_id_migrator_progress.exs b/apps/explorer/priv/repo/migrations/20221104104635_create_token_transfer_token_id_migrator_progress.exs new file mode 100644 index 000000000000..3bf19f8dfbda --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221104104635_create_token_transfer_token_id_migrator_progress.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.CreateTokenTransferTokenIdMigratorProgress do + use Ecto.Migration + + def change do + create table(:token_transfer_token_id_migrator_progress) do + add(:last_processed_block_number, :integer) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20221114113853_remove_not_null_constraint_from_abi.exs b/apps/explorer/priv/repo/migrations/20221114113853_remove_not_null_constraint_from_abi.exs new file mode 100644 index 000000000000..81903e79e30c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221114113853_remove_not_null_constraint_from_abi.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.RemoveNotNullConstraintFromAbi do + use Ecto.Migration + + def change do + execute("ALTER TABLE smart_contracts ALTER COLUMN abi DROP NOT NULL;") + end +end diff --git a/apps/explorer/priv/repo/migrations/20221114121811_drop_internal_transactions_order_index.exs b/apps/explorer/priv/repo/migrations/20221114121811_drop_internal_transactions_order_index.exs new file mode 100644 index 000000000000..f59a416be856 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221114121811_drop_internal_transactions_order_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.DropInternalTransactionsOrderIndex do + use Ecto.Migration + + def change do + drop_if_exists(index(:internal_transactions, ["block_number DESC, transaction_index DESC, index DESC"])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20221117075456_modify_address_token_balances_indexes.exs b/apps/explorer/priv/repo/migrations/20221117075456_modify_address_token_balances_indexes.exs new file mode 100644 index 000000000000..9c21c4b40a89 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221117075456_modify_address_token_balances_indexes.exs @@ -0,0 +1,58 @@ +defmodule Explorer.Repo.Migrations.ModifyAddressTokenBalancesIndexes do + use Ecto.Migration + + def change do + drop_if_exists( + unique_index( + :address_token_balances, + ~w(address_hash token_contract_address_hash block_number)a, + name: :fetched_token_balances, + where: "token_id IS NULL" + ) + ) + + drop_if_exists( + unique_index( + :address_token_balances, + ~w(address_hash token_contract_address_hash token_id block_number)a, + name: :fetched_token_balances_with_token_id, + where: "token_id IS NOT NULL" + ) + ) + + create_if_not_exists( + unique_index( + :address_token_balances, + [:address_hash, :token_contract_address_hash, "COALESCE(token_id, -1)", :block_number], + name: :fetched_token_balances + ) + ) + + drop_if_exists( + unique_index( + :address_token_balances, + ~w(address_hash token_contract_address_hash block_number)a, + name: :unfetched_token_balances, + where: "value_fetched_at IS NULL and token_id IS NULL" + ) + ) + + drop_if_exists( + unique_index( + :address_token_balances, + ~w(address_hash token_contract_address_hash token_id block_number)a, + name: :unfetched_token_balances_with_token_id, + where: "value_fetched_at IS NULL and token_id IS NOT NULL" + ) + ) + + create_if_not_exists( + unique_index( + :address_token_balances, + [:address_hash, :token_contract_address_hash, "COALESCE(token_id, -1)", :block_number], + name: :unfetched_token_balances, + where: "value_fetched_at IS NULL" + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20221117080657_modify_address_current_token_balances_indexes.exs b/apps/explorer/priv/repo/migrations/20221117080657_modify_address_current_token_balances_indexes.exs new file mode 100644 index 000000000000..295a7a5c3f15 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221117080657_modify_address_current_token_balances_indexes.exs @@ -0,0 +1,31 @@ +defmodule Explorer.Repo.Migrations.ModifyAddressCurrentTokenBalancesIndexes do + use Ecto.Migration + + def change do + drop_if_exists( + unique_index( + :address_current_token_balances, + ~w(address_hash token_contract_address_hash)a, + name: :fetched_current_token_balances, + where: "token_id IS NULL" + ) + ) + + drop_if_exists( + unique_index( + :address_current_token_balances, + ~w(address_hash token_contract_address_hash token_id)a, + name: :fetched_current_token_balances_with_token_id, + where: "token_id IS NOT NULL" + ) + ) + + create_if_not_exists( + unique_index( + :address_current_token_balances, + [:address_hash, :token_contract_address_hash, "COALESCE(token_id, -1)"], + name: :fetched_current_token_balances + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20221120184715_add_json_compiler_settings.exs b/apps/explorer/priv/repo/migrations/20221120184715_add_json_compiler_settings.exs new file mode 100644 index 000000000000..e6f90d78648a --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221120184715_add_json_compiler_settings.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddJsonCompilerSettings do + use Ecto.Migration + + def change do + alter table(:smart_contracts) do + add(:compiler_settings, :jsonb, null: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20221125074820_drop_required_output_constraint.exs b/apps/explorer/priv/repo/migrations/20221125074820_drop_required_output_constraint.exs new file mode 100644 index 000000000000..a524af715fc0 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221125074820_drop_required_output_constraint.exs @@ -0,0 +1,31 @@ +defmodule Explorer.Repo.Migrations.DropRequiredOutputConstraint do + use Ecto.Migration + + def change do + drop( + constraint( + :internal_transactions, + :call_has_error_or_result, + check: """ + type != 'call' OR + (gas IS NOT NULL AND + ((error IS NULL AND gas_used IS NOT NULL and output IS NOT NULL) OR + (error IS NOT NULL AND gas_used IS NULL and output is NULL))) + """ + ) + ) + + create( + constraint( + :internal_transactions, + :call_has_error_or_result, + check: """ + type != 'call' OR + (gas IS NOT NULL AND + ((error IS NULL AND gas_used IS NOT NULL AND output IS NOT NULL) OR + (error IS NOT NULL AND output is NULL))) + """ + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20221126103223_add_txs_indexes.exs b/apps/explorer/priv/repo/migrations/20221126103223_add_txs_indexes.exs new file mode 100644 index 000000000000..241ed059688c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221126103223_add_txs_indexes.exs @@ -0,0 +1,73 @@ +defmodule Explorer.Repo.Migrations.AddTxsIndexes do + use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true + + def change do + drop_if_exists( + index( + :transactions, + [:from_address_hash, "block_number DESC NULLS FIRST", "index DESC NULLS FIRST", :hash], + name: "transactions_from_address_hash_recent_collated_index", + concurrently: true + ) + ) + + drop_if_exists( + index( + :transactions, + [:to_address_hash, "block_number DESC NULLS FIRST", "index DESC NULLS FIRST", :hash], + name: "transactions_to_address_hash_recent_collated_index", + concurrently: true + ) + ) + + drop_if_exists( + index( + :transactions, + [:created_contract_address_hash, "block_number DESC NULLS FIRST", "index DESC NULLS FIRST", :hash], + name: "transactions_created_contract_address_hash_recent_collated_index", + concurrently: true + ) + ) + + create_if_not_exists( + index( + :transactions, + [ + :from_address_hash, + "block_number DESC NULLS FIRST", + "index DESC NULLS FIRST", + "inserted_at DESC", + "hash ASC" + ], + name: "transactions_from_address_hash_with_pending_index", + concurrently: true + ) + ) + + create_if_not_exists( + index( + :transactions, + [:to_address_hash, "block_number DESC NULLS FIRST", "index DESC NULLS FIRST", "inserted_at DESC", "hash ASC"], + name: "transactions_to_address_hash_with_pending_index", + concurrently: true + ) + ) + + create_if_not_exists( + index( + :transactions, + [ + :created_contract_address_hash, + "block_number DESC NULLS FIRST", + "index DESC NULLS FIRST", + "inserted_at DESC", + "hash ASC" + ], + name: "transactions_created_contract_address_hash_with_pending_index", + concurrently: true + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20221209123459_drop_unfetched_token_balances_index.exs b/apps/explorer/priv/repo/migrations/20221209123459_drop_unfetched_token_balances_index.exs new file mode 100644 index 000000000000..c4e89981297b --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221209123459_drop_unfetched_token_balances_index.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Migrations.DropUnfetchedTokenBalancesIndex do + use Ecto.Migration + + def change do + drop_if_exists( + unique_index( + :address_token_balances, + [:address_hash, :token_contract_address_hash, "COALESCE(token_id, -1)", :block_number], + name: :unfetched_token_balances, + where: "value_fetched_at IS NULL" + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20221212093406_change_index_for_pending_block_operations.exs b/apps/explorer/priv/repo/migrations/20221212093406_change_index_for_pending_block_operations.exs new file mode 100644 index 000000000000..550c6ce2b288 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20221212093406_change_index_for_pending_block_operations.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Repo.Migrations.ChangeIndexForPendingBlockOperations do + use Ecto.Migration + + def change do + drop_if_exists( + index( + :pending_block_operations, + [:block_hash], + name: "pending_block_operations_block_hash_index_partial", + where: ~s("fetch_internal_transactions") + ) + ) + + alter table(:pending_block_operations) do + remove(:fetch_internal_transactions) + end + end +end diff --git a/apps/explorer/test/explorer/account/notify/email_test.exs b/apps/explorer/test/explorer/account/notify/email_test.exs new file mode 100644 index 000000000000..34e5a57ad6d9 --- /dev/null +++ b/apps/explorer/test/explorer/account/notify/email_test.exs @@ -0,0 +1,124 @@ +defmodule Explorer.Account.Notify.EmailTest do + use ExUnit.Case + + alias Explorer.Chain.Address + alias Explorer.Chain.Transaction + + alias Explorer.Account.{ + Identity, + Watchlist, + WatchlistAddress, + WatchlistNotification + } + + import Explorer.Chain, + only: [ + string_to_address_hash: 1, + string_to_transaction_hash: 1 + ] + + import Explorer.Account.Notifier.Email, + only: [compose: 2] + + setup do + host = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + path = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] + + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, url: [host: "localhost", path: "/"]) + + Application.put_env(:explorer, Explorer.Account, + sendgrid: [ + sender: "noreply@blockscout.com", + template: "d-666" + ] + ) + + :ok + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, url: [host: host, path: path]) + end) + end + + describe "composing email" do + test "compose_email" do + {:ok, tx_hash} = string_to_transaction_hash("0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d") + + {:ok, from_hash} = string_to_address_hash("0x092D537737E767Dae48c28aE509f34094496f030") + + {:ok, to_hash} = string_to_address_hash("0xE1F4dd38f00B0D8D4d2b4B5010bE53F2A0b934E5") + to_address = %Address{hash: to_hash} + + identity = %Identity{ + uid: "foo|bar", + name: "John Snow", + email: "john@blockscout.com" + } + + watchlist = %Watchlist{identity: identity} + + watchlist_address = %WatchlistAddress{ + name: "wallet", + watchlist: watchlist, + address_hash: to_hash, + watch_coin_input: true, + watch_coin_output: true, + notify_email: true + } + + watchlist_notification = %WatchlistNotification{ + watchlist_address: watchlist_address, + transaction_hash: tx_hash, + from_address_hash: from_hash, + to_address_hash: to_hash, + direction: "incoming", + method: "transfer", + block_number: 24_121_177, + amount: Decimal.new(1), + tx_fee: Decimal.new(210_000), + name: "wallet", + type: "COIN" + } + + assert compose(watchlist_notification, watchlist_address) == + %Bamboo.Email{ + assigns: %{}, + attachments: [], + bcc: nil, + blocked: false, + cc: nil, + from: "noreply@blockscout.com", + headers: %{}, + html_body: nil, + private: %{ + send_grid_template: %{ + dynamic_template_data: %{ + "address_hash" => "0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5", + "address_name" => "wallet", + "address_url" => "https://localhost//address/0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5", + "amount" => Decimal.new(1), + "block_number" => 24_121_177, + "block_url" => "https://localhost/block/24121177", + "direction" => "received at", + "from_address_hash" => "0x092d537737e767dae48c28ae509f34094496f030", + "from_url" => "https://localhost//address/0x092d537737e767dae48c28ae509f34094496f030", + "method" => "transfer", + "name" => "wallet", + "to_address_hash" => "0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5", + "to_url" => "https://localhost//address/0xe1f4dd38f00b0d8d4d2b4b5010be53f2a0b934e5", + "transaction_hash" => "0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d", + "transaction_url" => + "https://localhost//tx/0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d", + "tx_fee" => Decimal.new(210_000), + "username" => "John Snow" + }, + template_id: "d-666" + } + }, + subject: nil, + text_body: nil, + to: "john@blockscout.com" + } + end + end +end diff --git a/apps/explorer/test/explorer/account/notify/notify_test.exs b/apps/explorer/test/explorer/account/notify/notify_test.exs new file mode 100644 index 000000000000..4d169f04d6b7 --- /dev/null +++ b/apps/explorer/test/explorer/account/notify/notify_test.exs @@ -0,0 +1,91 @@ +defmodule Explorer.Account.Notify.NotifyTest do + # use ExUnit.Case + use Explorer.DataCase + + import Explorer.Factory + + alias Explorer.Account.Notifier.Notify + alias Explorer.Account.{WatchlistAddress, WatchlistNotification} + alias Explorer.Chain + alias Explorer.Chain.{Transaction, Wei} + alias Explorer.Repo + + setup do + Application.put_env(:explorer, Explorer.Account, + sendgrid: [ + sender: "noreply@blockscout.com", + template: "d-666" + ] + ) + + Application.put_env(:explorer, Explorer.Mailer, + adapter: Bamboo.SendGridAdapter, + api_key: "SENDGRID_API_KEY" + ) + + Application.put_env( + :ueberauth, + Ueberauth, + providers: [ + auth0: { + Ueberauth.Strategy.Auth0, + [callback_url: "callback.url"] + } + ], + logout_url: "logout.url", + logout_return_to_url: "return.url" + ) + end + + describe "notify" do + test "when address not in any watchlist" do + tx = with_block(insert(:transaction)) + + notify = Notify.call([tx]) + + wn = + WatchlistNotification + |> first + |> Repo.account_repo().one() + + assert notify == [[:ok]] + + assert wn == nil + end + + test "when address apears in watchlist" do + wa = + %WatchlistAddress{address_hash: address_hash} = + build(:account_watchlist_address) + |> Repo.account_repo().insert!() + + _watchlist_address = Repo.preload(wa, watchlist: :identity) + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash})) + + {_, fee} = Chain.fee(tx, :gwei) + amount = Wei.to(tx.value, :ether) + notify = Notify.call([tx]) + + wn = + WatchlistNotification + |> first + |> Repo.account_repo().one() + + assert notify == [[:ok]] + + assert wn.amount == amount + assert wn.direction == "incoming" + assert wn.method == "transfer" + assert wn.subject == "Coin transaction" + assert wn.tx_fee == fee + assert wn.type == "COIN" + end + end +end diff --git a/apps/explorer/test/explorer/account/notify/summary_test.exs b/apps/explorer/test/explorer/account/notify/summary_test.exs new file mode 100644 index 000000000000..9fcfb9230d00 --- /dev/null +++ b/apps/explorer/test/explorer/account/notify/summary_test.exs @@ -0,0 +1,272 @@ +defmodule Explorer.Account.Notify.SummaryTest do + use Explorer.DataCase + + import Explorer.Factory + + alias Explorer.Account.Notifier.Summary + alias Explorer.Chain + alias Explorer.Chain.{TokenTransfer, Transaction, Wei} + alias Explorer.Repo + + describe "call" do + test "Coin transaction" do + tx = + %Transaction{ + from_address: from_address, + to_address: to_address, + block_number: block_number, + hash: tx_hash + } = with_block(insert(:transaction)) + + {_, fee} = Chain.fee(tx, :gwei) + amount = Wei.to(tx.value, :ether) + + assert Summary.process(tx) == [ + %Summary{ + amount: amount, + block_number: block_number, + from_address_hash: from_address.hash, + method: "transfer", + name: "ETH", + subject: "Coin transaction", + to_address_hash: to_address.hash, + transaction_hash: tx_hash, + tx_fee: fee, + type: "COIN" + } + ] + end + + test "Pending Coin transaction (w/o block)" do + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + hash: _tx_hash + } = insert(:transaction) + + assert Summary.process(tx) == [] + end + + test "Contract creation transaction" do + address = insert(:address) + contract_address = insert(:contract_address) + + block = insert(:block) + + tx = + %Transaction{ + from_address: _from_address, + block_number: _block_number, + hash: tx_hash + } = + :transaction + |> insert(from_address: address, to_address: nil) + |> with_contract_creation(contract_address) + |> with_block(block) + + {_, fee} = Chain.fee(tx, :gwei) + amount = Wei.to(tx.value, :ether) + + assert Summary.process(tx) == [ + %Summary{ + amount: amount, + block_number: block.number, + from_address_hash: address.hash, + method: "contract_creation", + name: "ETH", + subject: "Contract creation", + to_address_hash: contract_address.hash, + transaction_hash: tx_hash, + tx_fee: fee, + type: "COIN" + } + ] + end + + test "ERC-20 Token transfer" do + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction)) + + transfer = + %TokenTransfer{ + amount: _amount, + block_number: block_number, + from_address: from_address, + to_address: to_address, + token: token + } = + :token_transfer + |> insert(transaction: tx) + |> Repo.preload([ + :token + ]) + + {_, fee} = Chain.fee(tx, :gwei) + + token_decimals = Decimal.to_integer(token.decimals) + + decimals = Decimal.new(Integer.pow(10, token_decimals)) + + amount = Decimal.div(transfer.amount, decimals) + + assert Summary.process(transfer) == [ + %Summary{ + amount: amount, + block_number: block_number, + from_address_hash: from_address.hash, + method: "transfer", + name: "Infinite Token", + subject: "ERC-20", + to_address_hash: to_address.hash, + transaction_hash: tx.hash, + tx_fee: fee, + type: "ERC-20" + } + ] + end + + test "ERC-721 Token transfer" do + token = insert(:token, type: "ERC-721") + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction)) + + transfer = + %TokenTransfer{ + amount: _amount, + block_number: block_number, + from_address: from_address, + to_address: to_address + } = + :token_transfer + |> insert( + transaction: tx, + token_ids: [42], + token_contract_address: token.contract_address + ) + |> Repo.preload([ + :token + ]) + + {_, fee} = Chain.fee(tx, :gwei) + + assert Summary.process(transfer) == [ + %Summary{ + amount: 0, + block_number: block_number, + from_address_hash: from_address.hash, + method: "transfer", + name: "Infinite Token", + subject: "42", + to_address_hash: to_address.hash, + transaction_hash: tx.hash, + tx_fee: fee, + type: "ERC-721" + } + ] + end + + test "ERC-1155 single Token transfer" do + token = insert(:token, type: "ERC-1155") + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction)) + + transfer = + %TokenTransfer{ + amount: _amount, + block_number: block_number, + from_address: from_address, + to_address: to_address + } = + :token_transfer + |> insert( + transaction: tx, + token_ids: [42], + token_contract_address: token.contract_address + ) + |> Repo.preload([ + :token + ]) + + {_, fee} = Chain.fee(tx, :gwei) + + assert Summary.process(transfer) == [ + %Summary{ + amount: 0, + block_number: block_number, + from_address_hash: from_address.hash, + method: "transfer", + name: "Infinite Token", + subject: "42", + to_address_hash: to_address.hash, + transaction_hash: tx.hash, + tx_fee: fee, + type: "ERC-1155" + } + ] + end + + test "ERC-1155 multiple Token transfer" do + token = insert(:token, type: "ERC-1155") + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction)) + + transfer = + %TokenTransfer{ + amount: _amount, + block_number: block_number, + from_address: from_address, + to_address: to_address + } = + :token_transfer + |> insert( + transaction: tx, + token_ids: [23, 42], + token_contract_address: token.contract_address + ) + |> Repo.preload([ + :token + ]) + + {_, fee} = Chain.fee(tx, :gwei) + + assert Summary.process(transfer) == [ + %Summary{ + amount: 0, + block_number: block_number, + from_address_hash: from_address.hash, + method: "transfer", + name: "Infinite Token", + subject: "23, 42", + to_address_hash: to_address.hash, + transaction_hash: tx.hash, + tx_fee: fee, + type: "ERC-1155" + } + ] + end + end +end diff --git a/apps/explorer/test/explorer/accounts/accounts_test.exs b/apps/explorer/test/explorer/accounts/accounts_test.exs index e3ba1ed41669..37c61d414b47 100644 --- a/apps/explorer/test/explorer/accounts/accounts_test.exs +++ b/apps/explorer/test/explorer/accounts/accounts_test.exs @@ -19,7 +19,7 @@ defmodule Explorer.AccountsTest do assert user.username == params.username refute user.password_hash == params.password - assert Comeonin.Bcrypt.checkpw(params.password, user.password_hash) + assert Bcrypt.verify_pass(params.password, user.password_hash) assert contact.email == params.email assert contact.primary refute contact.verified diff --git a/apps/explorer/test/explorer/chain/address/token_balance_test.exs b/apps/explorer/test/explorer/chain/address/token_balance_test.exs index 4c59c675bf3d..717a783767a5 100644 --- a/apps/explorer/test/explorer/chain/address/token_balance_test.exs +++ b/apps/explorer/test/explorer/chain/address/token_balance_test.exs @@ -3,6 +3,7 @@ defmodule Explorer.Chain.Address.TokenBalanceTest do alias Explorer.Repo alias Explorer.Chain.Address.TokenBalance + alias Explorer.Chain describe "unfetched_token_balances/0" do test "returns only the token balances that have value_fetched_at nil" do @@ -76,4 +77,46 @@ defmodule Explorer.Chain.Address.TokenBalanceTest do assert result.block_number == token_balance.block_number end end + + describe "fetch_token_balance/3" do + test "returns the token balance for the given address" do + token_balance = insert(:token_balance) + + result = + TokenBalance.fetch_token_balance( + token_balance.address_hash, + token_balance.token_contract_address_hash, + token_balance.block_number + ) + |> Repo.one() + + assert(result.address_hash == token_balance.address_hash) + end + + test "returns the token balance only from block less or equal than given for the given address" do + address = insert(:address) + token_balance_a = insert(:token_balance, address: address, block_number: 10) + + result = + TokenBalance.fetch_token_balance( + token_balance_a.address_hash, + token_balance_a.token_contract_address_hash, + token_balance_a.block_number - 3 + ) + |> Repo.one() + + assert(is_nil(result)) + token_balance_b = insert(:token_balance, address: address, block_number: token_balance_a.block_number - 3) + + result = + TokenBalance.fetch_token_balance( + token_balance_b.address_hash, + token_balance_b.token_contract_address_hash, + token_balance_b.block_number + ) + |> Repo.one() + + assert(result.value == token_balance_b.value) + end + end end diff --git a/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs index 6ae71ddeabe0..34494dc932fb 100644 --- a/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs @@ -26,47 +26,50 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporterTest do from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) - [result] = + res = address |> AddressInternalTransactionCsvExporter.export(from_period, to_period) |> Enum.to_list() |> Enum.drop(1) + + [result] = + res |> Enum.map(fn [ - transaction_hash, + [[], transaction_hash], _, - index, + [[], index], _, - block_number, + [[], block_number], _, - block_hash, + [[], block_hash], _, - block_index, + [[], block_index], _, - transaction_index, + [[], transaction_index], _, - timestamp, + [[], timestamp], _, - from_address_hash, + [[], from_address_hash], _, - to_address_hash, + [[], to_address_hash], _, - created_contract_address_hash, + [[], created_contract_address_hash], _, - type, + [[], type], _, - call_type, + [[], call_type], _, - gas, + [[], gas], _, - gas_used, + [[], gas_used], _, - value, + [[], value], _, - input, + [[], input], _, - output, + [[], output], _, - error, + [[], error], _ ] -> %{ @@ -133,6 +136,44 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporterTest do end) |> Enum.count() + 1..200 + |> Enum.map(fn index -> + transaction = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + index: index, + transaction: transaction, + to_address: address, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: index, + transaction_index: transaction.index + ) + end) + |> Enum.count() + + 1..200 + |> Enum.map(fn index -> + transaction = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + index: index, + transaction: transaction, + created_contract_address: address, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: index, + transaction_index: transaction.index + ) + end) + |> Enum.count() + from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) @@ -142,7 +183,7 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporterTest do |> Enum.to_list() |> Enum.drop(1) - assert Enum.count(result) == 200 + assert Enum.count(result) == 600 end end end diff --git a/apps/explorer/test/explorer/chain/address_log_csv_explorer_test.exs b/apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs similarity index 87% rename from apps/explorer/test/explorer/chain/address_log_csv_explorer_test.exs rename to apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs index 32bc5b4bd796..fe1d0601a88e 100644 --- a/apps/explorer/test/explorer/chain/address_log_csv_explorer_test.exs +++ b/apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs @@ -35,25 +35,25 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do |> Enum.to_list() |> Enum.drop(1) |> Enum.map(fn [ - transaction_hash, + [[], transaction_hash], _, - index, + [[], index], _, - block_number, + [[], block_number], _, - block_hash, + [[], block_hash], _, - address, + [[], address], _, - data, + [[], data], _, - first_topic, + [[], first_topic], _, - second_topic, + [[], second_topic], _, - third_topic, + [[], third_topic], _, - fourth_topic, + [[], fourth_topic], _ ] -> %{ diff --git a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs index 99655c832ff8..c64ecafa99b8 100644 --- a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs @@ -24,29 +24,29 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do |> Enum.to_list() |> Enum.drop(1) |> Enum.map(fn [ - tx_hash, + [[], tx_hash], _, - block_number, + [[], block_number], _, - timestamp, + [[], timestamp], _, - from_address, + [[], from_address], _, - to_address, + [[], to_address], _, - token_contract_address, + [[], token_contract_address], _, - type, + [[], type], _, - token_symbol, + [[], token_symbol], _, - tokens_transferred, + [[], tokens_transferred], _, - transaction_fee, + [[], transaction_fee], _, - status, + [[], status], _, - err_code, + [[], err_code], _ ] -> %{ @@ -72,5 +72,50 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do assert result.timestamp == to_string(transaction.block.timestamp) assert result.type == "OUT" end + + test "fetches all token transfers" do + address = insert(:address) + + 1..200 + |> Enum.map(fn _ -> + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:token_transfer, + transaction: transaction, + from_address: address, + block_number: transaction.block_number + ) + end) + |> Enum.count() + + 1..200 + |> Enum.map(fn _ -> + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + insert(:token_transfer, + transaction: transaction, + to_address: address, + block_number: transaction.block_number + ) + end) + |> Enum.count() + + from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) + to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) + + result = + address + |> AddressTokenTransferCsvExporter.export(from_period, to_period) + |> Enum.to_list() + |> Enum.drop(1) + + assert Enum.count(result) == 400 + end end end diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index 684be20f9cfc..7145e9c507f4 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -22,33 +22,33 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do |> Enum.to_list() |> Enum.drop(1) |> Enum.map(fn [ - hash, + [[], hash], _, - block_number, + [[], block_number], _, - timestamp, + [[], timestamp], _, - from_address, + [[], from_address], _, - to_address, + [[], to_address], _, - created_address, + [[], created_address], _, - type, + [[], type], _, - value, + [[], value], _, - fee, + [[], fee], _, - status, + [[], status], _, - error, + [[], error], _, - cur_price, + [[], cur_price], _, - op_price, + [[], op_price], _, - cl_price, + [[], cl_price], _ ] -> %{ @@ -96,6 +96,22 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do end) |> Enum.count() + 1..200 + |> Enum.map(fn _ -> + :transaction + |> insert(to_address: address) + |> with_block() + end) + |> Enum.count() + + 1..200 + |> Enum.map(fn _ -> + :transaction + |> insert(created_contract_address: address) + |> with_block() + end) + |> Enum.count() + from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) @@ -105,7 +121,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do |> Enum.to_list() |> Enum.drop(1) - assert Enum.count(result) == 200 + assert Enum.count(result) == 600 end end end diff --git a/apps/explorer/test/explorer/chain/cache/contracts_counter_test.exs b/apps/explorer/test/explorer/chain/cache/contracts_counter_test.exs new file mode 100644 index 000000000000..aad5c48a06b8 --- /dev/null +++ b/apps/explorer/test/explorer/chain/cache/contracts_counter_test.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Chain.Cache.ContractsCounterTest do + use Explorer.DataCase + + alias Explorer.Chain.Cache.ContractsCounter + alias Explorer.Chain + + test "populates the cache with the number of all contracts" do + insert(:address, contract_code: "0x608060") + insert(:address, contract_code: "0x608060") + insert(:address, contract_code: "0x608060", inserted_at: Timex.shift(Timex.now(), days: -2)) + insert(:smart_contract) + + start_supervised!(ContractsCounter) + ContractsCounter.consolidate() + + assert Chain.count_contracts_from_cache() == Decimal.new(4) + end +end diff --git a/apps/explorer/test/explorer/chain/cache/new_contracts_counter_test.exs b/apps/explorer/test/explorer/chain/cache/new_contracts_counter_test.exs new file mode 100644 index 000000000000..b21d87d42013 --- /dev/null +++ b/apps/explorer/test/explorer/chain/cache/new_contracts_counter_test.exs @@ -0,0 +1,29 @@ +defmodule Explorer.Chain.Cache.NewContractsCounterTest do + use Explorer.DataCase + + alias Explorer.Chain.Cache.NewContractsCounter + alias Explorer.Chain + + test "populates the cache with the number of new contracts (last 24h)" do + :transaction + |> insert(created_contract_code_indexed_at: Timex.shift(Timex.now(), hours: -1)) + |> with_block(status: :ok) + + :transaction + |> insert(created_contract_code_indexed_at: Timex.shift(Timex.now(), hours: -25)) + |> with_block(status: :ok) + + :transaction + |> insert(created_contract_code_indexed_at: Timex.shift(Timex.now(), hours: -23)) + |> with_block(status: :ok) + + :transaction + |> insert(created_contract_code_indexed_at: Timex.shift(Timex.now(), hours: -30)) + |> with_block(status: :ok) + + start_supervised!(NewContractsCounter) + NewContractsCounter.consolidate() + + assert Chain.count_new_contracts_from_cache() == Decimal.new(2) + end +end diff --git a/apps/explorer/test/explorer/chain/cache/new_verified_contracts_counter_test.exs b/apps/explorer/test/explorer/chain/cache/new_verified_contracts_counter_test.exs new file mode 100644 index 000000000000..cb963fc3264c --- /dev/null +++ b/apps/explorer/test/explorer/chain/cache/new_verified_contracts_counter_test.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Chain.Cache.NewVerifiedContractsCounterTest do + use Explorer.DataCase + + alias Explorer.Chain.Cache.NewVerifiedContractsCounter + alias Explorer.Chain + + test "populates the cache with the number of new verified contracts (last 24h)" do + insert(:smart_contract, inserted_at: Timex.shift(Timex.now(), hours: -25)) + insert(:smart_contract, inserted_at: Timex.shift(Timex.now(), hours: -1)) + insert(:smart_contract, inserted_at: Timex.shift(Timex.now(), hours: -23)) + insert(:smart_contract, inserted_at: Timex.shift(Timex.now(), hours: -30)) + + start_supervised!(NewVerifiedContractsCounter) + NewVerifiedContractsCounter.consolidate() + + assert Chain.count_new_verified_contracts_from_cache() == Decimal.new(2) + end +end diff --git a/apps/explorer/test/explorer/chain/cache/verified_contracts_counter_test.exs b/apps/explorer/test/explorer/chain/cache/verified_contracts_counter_test.exs new file mode 100644 index 000000000000..b742d9c338bf --- /dev/null +++ b/apps/explorer/test/explorer/chain/cache/verified_contracts_counter_test.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Chain.Cache.VerifiedContractsCounterTest do + use Explorer.DataCase + + alias Explorer.Chain.Cache.VerifiedContractsCounter + alias Explorer.Chain + + test "populates the cache with the number of verified contracts" do + insert(:smart_contract) + insert(:smart_contract) + insert(:smart_contract, inserted_at: Timex.shift(Timex.now(), days: -2)) + + start_supervised!(VerifiedContractsCounter) + VerifiedContractsCounter.consolidate() + + assert Chain.count_verified_contracts_from_cache() == Decimal.new(3) + end +end diff --git a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs index 7c34c72719cd..31eba0553de0 100644 --- a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs @@ -97,30 +97,30 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do %Explorer.Chain.Address.CurrentTokenBalance{ address_hash: ^address_hash, block_number: ^block_number, - token_contract_address_hash: ^token_erc_20_contract_address_hash, - value: ^value_3, - token_id: ^token_id_3 + token_contract_address_hash: ^token_contract_address_hash, + value: ^value_1, + token_id: ^token_id_1 }, %Explorer.Chain.Address.CurrentTokenBalance{ address_hash: ^address_hash, block_number: ^block_number, - token_contract_address_hash: ^token_erc_721_contract_address_hash, - value: ^value_5, - token_id: nil + token_contract_address_hash: ^token_contract_address_hash, + value: ^value_2, + token_id: ^token_id_2 }, %Explorer.Chain.Address.CurrentTokenBalance{ address_hash: ^address_hash, block_number: ^block_number, - token_contract_address_hash: ^token_contract_address_hash, - value: ^value_1, - token_id: ^token_id_1 + token_contract_address_hash: ^token_erc_20_contract_address_hash, + value: ^value_3, + token_id: ^token_id_3 }, %Explorer.Chain.Address.CurrentTokenBalance{ address_hash: ^address_hash, block_number: ^block_number, - token_contract_address_hash: ^token_contract_address_hash, - value: ^value_2, - token_id: ^token_id_2 + token_contract_address_hash: ^token_erc_721_contract_address_hash, + value: ^value_5, + token_id: nil } ], address_current_token_balances_update_token_holder_counts: [ @@ -172,7 +172,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do block_number: block_number, token_contract_address_hash: token_erc_721.contract_address_hash, value: value_4, - value_fetched_at: DateTime.utc_now(), + value_fetched_at: DateTime.add(DateTime.utc_now(), -1), token_id: token_id_4, token_type: "ERC-721" }, diff --git a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs index f44b6bca792f..15725869bd85 100644 --- a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs @@ -41,6 +41,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do address_hash: ^address_hash, block_number: ^block_number, token_contract_address_hash: ^token_contract_address_hash, + token_id: nil, value: ^value, value_fetched_at: ^value_fetched_at } @@ -83,6 +84,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do address_hash: address_hash, block_number: ^block_number, token_contract_address_hash: ^token_contract_address_hash, + token_id: nil, value: nil, value_fetched_at: ^value_fetched_at } @@ -153,6 +155,70 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do run_changes(new_changes, options) end + test "set value_fetched_at to null for existing record if incoming data has this field empty" do + address = insert(:address) + token = insert(:token) + + options = %{ + timeout: :infinity, + timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()} + } + + block_number = 1 + + value = Decimal.new(100) + value_fetched_at = DateTime.utc_now() + + token_contract_address_hash = token.contract_address_hash + address_hash = address.hash + + first_changes = %{ + address_hash: address_hash, + block_number: block_number, + token_contract_address_hash: token_contract_address_hash, + token_id: 11, + token_type: "ERC-721", + value: value, + value_fetched_at: value_fetched_at + } + + assert {:ok, + %{ + address_token_balances: [ + %TokenBalance{ + address_hash: address_hash, + block_number: ^block_number, + token_contract_address_hash: ^token_contract_address_hash, + token_id: nil, + value: ^value, + value_fetched_at: ^value_fetched_at + } + ] + }} = run_changes(first_changes, options) + + second_changes = %{ + address_hash: address_hash, + block_number: block_number, + token_contract_address_hash: token_contract_address_hash, + token_id: 12, + token_type: "ERC-721" + } + + assert {:ok, + %{ + address_token_balances: [ + %TokenBalance{ + address_hash: address_hash, + block_number: ^block_number, + token_contract_address_hash: ^token_contract_address_hash, + token_id: nil, + value: ^value, + value_fetched_at: nil + } + ] + }} = run_changes(second_changes, options) + end + defp run_changes(changes, options) when is_map(changes) do run_changes_list([changes], options) end diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs index 6f5b53ac7306..5e41430ed8c7 100644 --- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs @@ -8,7 +8,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do describe "run/1" do test "transaction's status becomes :error when its internal_transaction has an error" do transaction = insert(:transaction) |> with_block(status: :ok) - insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: transaction.block_hash) assert :ok == transaction.status @@ -24,7 +24,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do test "transaction's has_error_in_internal_txs become true when its internal_transaction (where index != 0) has an error" do transaction = insert(:transaction) |> with_block(status: :ok) - insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: transaction.block_hash) assert :ok == transaction.status assert nil == transaction.has_error_in_internal_txs @@ -48,7 +48,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do test "transaction's has_error_in_internal_txs become false when its internal_transaction (where index == 0) has an error" do transaction = insert(:transaction) |> with_block(status: :ok) - insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: transaction.block_hash) assert :ok == transaction.status assert nil == transaction.has_error_in_internal_txs @@ -67,7 +67,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do test "transaction's has_error_in_internal_txs become false when its internal_transaction has no error" do transaction = insert(:transaction) |> with_block(status: :ok) - insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: transaction.block_hash) assert :ok == transaction.status assert nil == transaction.has_error_in_internal_txs @@ -92,7 +92,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do test "simple coin transfer's status becomes :error when its internal_transaction has an error" do transaction = insert(:transaction) |> with_block(status: :ok) - insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: transaction.block_hash) assert :ok == transaction.status @@ -112,7 +112,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do transaction1 = insert(:transaction) |> with_block(a_block, status: :ok) transaction2 = insert(:transaction) |> with_block(a_block, status: :ok) - insert(:pending_block_operation, block_hash: a_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: a_block.hash) assert :ok == transaction1.status assert :ok == transaction2.status @@ -136,7 +136,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do a_block = insert(:block, number: 1000) transaction1 = insert(:transaction) |> with_block(a_block, status: :ok) transaction2 = insert(:transaction) |> with_block(a_block, status: :ok) - insert(:pending_block_operation, block_hash: a_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: a_block.hash) assert :ok == transaction1.status assert :ok == transaction2.status @@ -161,7 +161,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do transaction0 = insert(:transaction) |> with_block(a_block, status: :ok) transaction1 = insert(:transaction) |> with_block(a_block, status: :ok) transaction2 = insert(:transaction) |> with_block(a_block, status: :ok) - insert(:pending_block_operation, block_hash: a_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: a_block.hash) assert :ok == transaction0.status assert :ok == transaction1.status @@ -203,7 +203,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do test "simple coin transfer has no internal transaction inserted" do transaction = insert(:transaction) |> with_block(status: :ok) - insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: transaction.block_hash) assert :ok == transaction.status @@ -221,7 +221,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do transaction = insert(:transaction) |> with_block(status: :ok) pending = insert(:transaction) - insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: transaction.block_hash) assert :ok == transaction.status assert is_nil(pending.block_hash) @@ -246,14 +246,14 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do empty_block = insert(:block) pending = insert(:transaction) - insert(:pending_block_operation, block_hash: empty_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: empty_block.hash) assert is_nil(pending.block_hash) full_block = insert(:block) inserted = insert(:transaction) |> with_block(full_block) - insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: full_block.hash) assert full_block.hash == inserted.block_hash @@ -290,7 +290,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do block_index: 0 ) - insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: full_block.hash) transaction_changes = make_internal_transaction_changes(transaction, 0, nil) @@ -309,7 +309,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do transaction_a = insert(:transaction) |> with_block(full_block) transaction_b = insert(:transaction) |> with_block(full_block) - insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: full_block.hash) transaction_a_changes = make_internal_transaction_changes(transaction_a, 0, nil) @@ -325,12 +325,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do test "does not remove consensus when block is empty and no transactions are missing" do empty_block = insert(:block) - insert(:pending_block_operation, block_hash: empty_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: empty_block.hash) full_block = insert(:block) inserted = insert(:transaction) |> with_block(full_block) - insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: full_block.hash) assert full_block.hash == inserted.block_hash diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index c5ce4e902402..8078cb33dd9f 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -648,11 +648,11 @@ defmodule Explorer.Chain.ImportTest do assert {:ok, _} = Import.all(options) {:ok, block_hash_casted} = Explorer.Chain.Hash.Full.cast(block_hash) - assert [^block_hash_casted] = Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions)) + assert [^block_hash_casted] = Explorer.Repo.all(PendingBlockOperation.block_hashes()) assert {:ok, _} = Import.all(internal_txs_options) - assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions)) + assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes()) end test "blocks with simple coin transfers updates PendingBlockOperation status" do @@ -738,11 +738,11 @@ defmodule Explorer.Chain.ImportTest do assert {:ok, _} = Import.all(options) {:ok, block_hash_casted} = Explorer.Chain.Hash.Full.cast(block_hash) - assert [^block_hash_casted] = Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions)) + assert [^block_hash_casted] = Explorer.Repo.all(PendingBlockOperation.block_hashes()) assert {:ok, _} = Import.all(internal_txs_options) - assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions)) + assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes()) end test "when the transaction has no to_address and an internal transaction with type create it populates the denormalized created_contract_address_hash" do diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs new file mode 100644 index 000000000000..b0744f9d8328 --- /dev/null +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -0,0 +1,403 @@ +defmodule Explorer.Chain.SmartContractTest do + use Explorer.DataCase, async: false + + import Mox + alias Explorer.Chain + alias Explorer.Chain.SmartContract + + doctest Explorer.Chain.SmartContract + + setup :verify_on_exit! + setup :set_mox_global + + describe "test fetching implementation" do + test "check proxy_contract/1 function" do + smart_contract = insert(:smart_contract) + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + + refute smart_contract.implementation_fetched_at + + # fetch nil implementation and save it to db + get_eip1967_implementation_zero_addresses() + refute SmartContract.proxy_contract?(smart_contract) + verify!(EthereumJSONRPC.Mox) + assert_empty_implementation(smart_contract.address_hash) + # extract proxy info from db + refute SmartContract.proxy_contract?(smart_contract) + verify!(EthereumJSONRPC.Mox) + assert_empty_implementation(smart_contract.address_hash) + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + + get_eip1967_implementation_error_response() + refute SmartContract.proxy_contract?(smart_contract) + verify!(EthereumJSONRPC.Mox) + + get_eip1967_implementation_non_zero_address() + assert SmartContract.proxy_contract?(smart_contract) + verify!(EthereumJSONRPC.Mox) + assert_implementation_address(smart_contract.address_hash) + + get_eip1967_implementation_non_zero_address() + assert SmartContract.proxy_contract?(smart_contract) + verify!(EthereumJSONRPC.Mox) + assert_implementation_address(smart_contract.address_hash) + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + assert SmartContract.proxy_contract?(smart_contract) + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + get_eip1967_implementation_non_zero_address() + assert SmartContract.proxy_contract?(smart_contract) + verify!(EthereumJSONRPC.Mox) + + get_eip1967_implementation_error_response() + assert SmartContract.proxy_contract?(smart_contract) + verify!(EthereumJSONRPC.Mox) + end + + test "test get_implementation_adddress_hash/1" do + smart_contract = insert(:smart_contract) + implementation_smart_contract = insert(:smart_contract, name: "proxy") + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + + refute smart_contract.implementation_fetched_at + + # fetch nil implementation and save it to db + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) + verify!(EthereumJSONRPC.Mox) + assert_empty_implementation(smart_contract.address_hash) + + # extract proxy info from db + assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) + assert_empty_implementation(smart_contract.address_hash) + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + + string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, string_implementation_address_hash} + end) + + assert {^string_implementation_address_hash, "proxy"} = + SmartContract.get_implementation_address_hash(smart_contract) + + verify!(EthereumJSONRPC.Mox) + + assert_exact_name_and_address( + smart_contract.address_hash, + implementation_smart_contract.address_hash, + implementation_smart_contract.name + ) + + get_eip1967_implementation_error_response() + + assert {^string_implementation_address_hash, "proxy"} = + SmartContract.get_implementation_address_hash(smart_contract) + + verify!(EthereumJSONRPC.Mox) + + assert_exact_name_and_address( + smart_contract.address_hash, + implementation_smart_contract.address_hash, + implementation_smart_contract.name + ) + + contract_1 = Chain.address_hash_to_smart_contract(smart_contract.address_hash) + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + + assert {^string_implementation_address_hash, "proxy"} = + SmartContract.get_implementation_address_hash(smart_contract) + + contract_2 = Chain.address_hash_to_smart_contract(smart_contract.address_hash) + + assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at && + contract_1.updated_at == contract_2.updated_at + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) + verify!(EthereumJSONRPC.Mox) + assert_empty_implementation(smart_contract.address_hash) + end + + test "test get_implementation_adddress_hash/1 for twins contract" do + # return nils for nil + assert {nil, nil} = SmartContract.get_implementation_address_hash(nil) + smart_contract = insert(:smart_contract) + another_address = insert(:contract_address) + + twin = Chain.address_hash_to_smart_contract(another_address.hash) + implementation_smart_contract = insert(:smart_contract, name: "proxy") + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + + # fetch nil implementation + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + verify!(EthereumJSONRPC.Mox) + assert_implementation_never_fetched(smart_contract.address_hash) + + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + verify!(EthereumJSONRPC.Mox) + assert_implementation_never_fetched(smart_contract.address_hash) + + string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, string_implementation_address_hash} + end) + + assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin) + + verify!(EthereumJSONRPC.Mox) + + assert_implementation_never_fetched(smart_contract.address_hash) + + get_eip1967_implementation_error_response() + + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + + verify!(EthereumJSONRPC.Mox) + + assert_implementation_never_fetched(smart_contract.address_hash) + + {:ok, addr} = Chain.hash_to_address(another_address.hash) + twin = addr.smart_contract + + implementation_smart_contract = insert(:smart_contract, name: "proxy") + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + + # fetch nil implementation + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + verify!(EthereumJSONRPC.Mox) + assert_implementation_never_fetched(smart_contract.address_hash) + + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + verify!(EthereumJSONRPC.Mox) + assert_implementation_never_fetched(smart_contract.address_hash) + + string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, string_implementation_address_hash} + end) + + assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin) + + verify!(EthereumJSONRPC.Mox) + + assert_implementation_never_fetched(smart_contract.address_hash) + + get_eip1967_implementation_error_response() + + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + + verify!(EthereumJSONRPC.Mox) + + assert_implementation_never_fetched(smart_contract.address_hash) + + {:ok, addr} = + Chain.find_contract_address( + another_address.hash, + [ + necessity_by_association: %{ + :smart_contract => :optional + } + ], + true + ) + + twin = addr.smart_contract + + implementation_smart_contract = insert(:smart_contract, name: "proxy") + + Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + + # fetch nil implementation + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + verify!(EthereumJSONRPC.Mox) + assert_implementation_never_fetched(smart_contract.address_hash) + + get_eip1967_implementation_zero_addresses() + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + verify!(EthereumJSONRPC.Mox) + assert_implementation_never_fetched(smart_contract.address_hash) + + string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, string_implementation_address_hash} + end) + + assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin) + + verify!(EthereumJSONRPC.Mox) + + assert_implementation_never_fetched(smart_contract.address_hash) + + get_eip1967_implementation_error_response() + + assert {nil, nil} = SmartContract.get_implementation_address_hash(twin) + + verify!(EthereumJSONRPC.Mox) + + assert_implementation_never_fetched(smart_contract.address_hash) + end + end + + def get_eip1967_implementation_zero_addresses do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + def get_eip1967_implementation_non_zero_address do + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} + end) + end + + def get_eip1967_implementation_error_response do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:error, "error"} + end) + end + + def assert_empty_implementation(address_hash) do + contract = Chain.address_hash_to_smart_contract(address_hash) + assert contract.implementation_fetched_at + refute contract.implementation_name + refute contract.implementation_address_hash + end + + def assert_implementation_never_fetched(address_hash) do + contract = Chain.address_hash_to_smart_contract(address_hash) + refute contract.implementation_fetched_at + refute contract.implementation_name + refute contract.implementation_address_hash + end + + def assert_implementation_address(address_hash) do + contract = Chain.address_hash_to_smart_contract(address_hash) + assert contract.implementation_fetched_at + assert contract.implementation_address_hash + end + + def assert_implementation_name(address_hash) do + contract = Chain.address_hash_to_smart_contract(address_hash) + assert contract.implementation_fetched_at + assert contract.implementation_name + end + + def assert_exact_name_and_address(address_hash, implementation_address_hash, implementation_name) do + contract = Chain.address_hash_to_smart_contract(address_hash) + assert contract.implementation_fetched_at + assert contract.implementation_name == implementation_name + assert to_string(contract.implementation_address_hash) == to_string(implementation_address_hash) + end +end diff --git a/apps/explorer/test/explorer/chain/supply/rsk_test.exs b/apps/explorer/test/explorer/chain/supply/rsk_test.exs index 6efc2bb844bb..a2e59294d221 100644 --- a/apps/explorer/test/explorer/chain/supply/rsk_test.exs +++ b/apps/explorer/test/explorer/chain/supply/rsk_test.exs @@ -14,7 +14,7 @@ defmodule Explorer.Chain.Supply.RSKTest do end describe "market_cap/1" do - @tag :no_parity + @tag :no_nethermind @tag :no_geth test "calculates market_cap" do EthereumJSONRPC.Mox diff --git a/apps/explorer/test/explorer/chain/token_transfer_test.exs b/apps/explorer/test/explorer/chain/token_transfer_test.exs index f82ab71323e5..da4720281a70 100644 --- a/apps/explorer/test/explorer/chain/token_transfer_test.exs +++ b/apps/explorer/test/explorer/chain/token_transfer_test.exs @@ -146,56 +146,6 @@ defmodule Explorer.Chain.TokenTransferTest do end end - describe "address_to_unique_tokens/2" do - test "returns list of unique tokens for a token contract" do - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address, type: "ERC-721") - - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1)) - - insert( - :token_instance, - token_id: 42, - token_contract_address_hash: token_contract_address.hash - ) - - insert( - :token_transfer, - to_address: build(:address), - transaction: transaction, - token_contract_address: token_contract_address, - token: token, - token_id: 42 - ) - - another_transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 3)) - - last_owner = - insert( - :token_transfer, - to_address: build(:address), - transaction: another_transaction, - token_contract_address: token_contract_address, - token: token, - token_id: 42 - ) - - results = - token_contract_address.hash - |> TokenTransfer.address_to_unique_tokens() - |> Repo.all() - - assert Enum.map(results, & &1.token_id) == [last_owner.token_id] - assert Enum.map(results, & &1.to_address_hash) == [last_owner.to_address_hash] - end - end - describe "where_any_address_fields_match/3" do test "when to_address_hash match returns transactions hashes list" do john = insert(:address) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index b64bf7ce41a9..c67c9b16cf39 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -44,10 +44,10 @@ defmodule Explorer.ChainTest do describe "remove_nonconsensus_blocks_from_pending_ops/0" do test "removes pending ops for nonconsensus blocks" do block = insert(:block) - insert(:pending_block_operation, block: block, fetch_internal_transactions: true) + insert(:pending_block_operation, block: block) nonconsensus_block = insert(:block, consensus: false) - insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true) + insert(:pending_block_operation, block: nonconsensus_block) :ok = Chain.remove_nonconsensus_blocks_from_pending_ops() @@ -57,13 +57,13 @@ defmodule Explorer.ChainTest do test "removes pending ops for nonconsensus blocks by block hashes" do block = insert(:block) - insert(:pending_block_operation, block: block, fetch_internal_transactions: true) + insert(:pending_block_operation, block: block) nonconsensus_block = insert(:block, consensus: false) - insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true) + insert(:pending_block_operation, block: nonconsensus_block) nonconsensus_block1 = insert(:block, consensus: false) - insert(:pending_block_operation, block: nonconsensus_block1, fetch_internal_transactions: true) + insert(:pending_block_operation, block: nonconsensus_block1) :ok = Chain.remove_nonconsensus_blocks_from_pending_ops([nonconsensus_block1.hash]) @@ -137,20 +137,22 @@ defmodule Explorer.ChainTest do end end - describe "ERC721_token_instance_from_token_id_and_token_address/2" do + describe "ERC721_or_ERC1155_token_instance_from_token_id_and_token_address/2" do test "return ERC721 token instance" do - contract_address = insert(:address) + token = insert(:token) token_id = 10 - insert(:token_transfer, - from_address: contract_address, - token_contract_address: contract_address, + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash, token_id: token_id ) assert {:ok, result} = - Chain.erc721_token_instance_from_token_id_and_token_address(token_id, contract_address.hash) + Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address( + token_id, + token.contract_address_hash + ) assert result.token_id == Decimal.new(token_id) end @@ -1185,7 +1187,7 @@ defmodule Explorer.ChainTest do end end - describe "finished_indexing?/0" do + describe "finished_internal_transactions_indexing?/0" do test "finished indexing" do block = insert(:block, number: 1) @@ -1193,11 +1195,11 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - assert Chain.finished_indexing?() + assert Chain.finished_internal_transactions_indexing?() end test "finished indexing (no txs)" do - assert Chain.finished_indexing?() + assert Chain.finished_internal_transactions_indexing?() end test "not finished indexing" do @@ -1207,9 +1209,9 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - insert(:pending_block_operation, block: block, fetch_internal_transactions: true) + insert(:pending_block_operation, block: block) - refute Chain.finished_indexing?() + refute Chain.finished_internal_transactions_indexing?() end end @@ -1466,17 +1468,23 @@ defmodule Explorer.ChainTest do end end - describe "indexed_ratio/0" do + describe "indexed_ratio_blocks/0" do + setup do + on_exit(fn -> + Application.put_env(:indexer, :first_block, "") + end) + end + test "returns indexed ratio" do for index <- 5..9 do insert(:block, number: index) end - assert Decimal.compare(Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq + assert Decimal.compare(Chain.indexed_ratio_blocks(), Decimal.from_float(0.5)) == :eq end test "returns 0 if no blocks" do - assert Decimal.new(0) == Chain.indexed_ratio() + assert Decimal.new(0) == Chain.indexed_ratio_blocks() end test "returns 1.0 if fully indexed blocks" do @@ -1485,7 +1493,60 @@ defmodule Explorer.ChainTest do Process.sleep(200) end - assert Decimal.compare(Chain.indexed_ratio(), 1) == :eq + assert Decimal.compare(Chain.indexed_ratio_blocks(), 1) == :eq + end + + test "returns 1.0 if fully indexed blocks starting from given FIRST_BLOCK" do + Application.put_env(:indexer, :first_block, "5") + + for index <- 5..9 do + insert(:block, number: index) + Process.sleep(200) + end + + assert Decimal.compare(Chain.indexed_ratio_blocks(), 1) == :eq + end + end + + describe "indexed_ratio_internal_transactions/0" do + setup do + on_exit(fn -> + Application.put_env(:indexer, :trace_first_block, "") + end) + end + + test "returns indexed ratio" do + for index <- 0..9 do + block = insert(:block, number: index) + + if index === 0 || index === 5 || index === 7 do + insert(:pending_block_operation, block: block) + end + end + + assert Decimal.compare(Chain.indexed_ratio_internal_transactions(), Decimal.from_float(0.7)) == :eq + end + + test "returns 0 if no blocks" do + assert Decimal.new(0) == Chain.indexed_ratio_internal_transactions() + end + + test "returns 1.0 if no pending block operations" do + for index <- 0..9 do + insert(:block, number: index) + end + + assert Decimal.compare(Chain.indexed_ratio_internal_transactions(), 1) == :eq + end + + test "returns 1.0 if fully indexed blocks with internal transactions starting from given TRACE_FIRST_BLOCK" do + Application.put_env(:indexer, :trace_first_block, "5") + + for index <- 5..9 do + insert(:block, number: index) + end + + assert Decimal.compare(Chain.indexed_ratio_internal_transactions(), 1) == :eq end end @@ -3292,7 +3353,7 @@ defmodule Explorer.ChainTest do assert [ %TokenTransfer{ - token: %Ecto.Association.NotLoaded{}, + token: %Token{}, transaction: %Ecto.Association.NotLoaded{} } ] = Chain.transaction_to_token_transfers(transaction.hash) @@ -3812,12 +3873,12 @@ defmodule Explorer.ChainTest do describe "recent_collated_transactions/1" do test "with no collated transactions it returns an empty list" do - assert [] == Explorer.Chain.recent_collated_transactions() + assert [] == Explorer.Chain.recent_collated_transactions(true) end test "it excludes pending transactions" do insert(:transaction) - assert [] == Explorer.Chain.recent_collated_transactions() + assert [] == Explorer.Chain.recent_collated_transactions(true) end test "returns a list of recent collated transactions" do @@ -3829,7 +3890,7 @@ defmodule Explorer.ChainTest do oldest_seen = Enum.at(newest_first_transactions, 9) paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}} - recent_collated_transactions = Explorer.Chain.recent_collated_transactions(paging_options: paging_options) + recent_collated_transactions = Explorer.Chain.recent_collated_transactions(true, paging_options: paging_options) assert length(recent_collated_transactions) == 10 assert hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash @@ -3851,10 +3912,11 @@ defmodule Explorer.ChainTest do to_address: address, transaction: transaction, token_contract_address: token_contract_address, - token: token + token: token, + block: transaction.block ) - fetched_transaction = List.first(Explorer.Chain.recent_collated_transactions()) + fetched_transaction = List.first(Explorer.Chain.recent_collated_transactions(true)) assert fetched_transaction.hash == transaction.hash assert length(fetched_transaction.token_transfers) == 2 end @@ -4802,7 +4864,7 @@ defmodule Explorer.ChainTest do end describe "stream_unfetched_token_instances/2" do - test "reduces wuth given reducer and accumulator" do + test "reduces with given reducer and accumulator for ERC-721 token" do token_contract_address = insert(:contract_address) token = insert(:token, contract_address: token_contract_address, type: "ERC-721") @@ -4819,15 +4881,15 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_id: 11 + token_ids: [11] ) assert {:ok, [result]} = Chain.stream_unfetched_token_instances([], &[&1 | &2]) - assert result.token_id == token_transfer.token_id + assert result.token_id == List.first(token_transfer.token_ids) assert result.contract_address_hash == token_transfer.token_contract_address_hash end - test "does not fetch token transfers without token id" do + test "does not fetch token transfers without token_ids" do token_contract_address = insert(:contract_address) token = insert(:token, contract_address: token_contract_address, type: "ERC-721") @@ -4843,7 +4905,7 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_id: nil + token_ids: nil ) assert {:ok, []} = Chain.stream_unfetched_token_instances([], &[&1 | &2]) @@ -4866,11 +4928,11 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_id: 11 + token_ids: [11] ) insert(:token_instance, - token_id: token_transfer.token_id, + token_id: List.first(token_transfer.token_ids), token_contract_address_hash: token_transfer.token_contract_address_hash ) @@ -5227,7 +5289,7 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_id: 29 + token_ids: [29] ) second_page = @@ -5238,17 +5300,17 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_id: 11 + token_ids: [11] ) - paging_options = %PagingOptions{key: {first_page.token_id}, page_size: 1} + paging_options = %PagingOptions{key: {List.first(first_page.token_ids)}, page_size: 1} unique_tokens_ids_paginated = token_contract_address.hash |> Chain.address_to_unique_tokens(paging_options: paging_options) |> Enum.map(& &1.token_id) - assert unique_tokens_ids_paginated == [second_page.token_id] + assert unique_tokens_ids_paginated == [List.first(second_page.token_ids)] end end @@ -5508,7 +5570,7 @@ defmodule Explorer.ChainTest do refute Chain.contract_address?(to_string(address.hash), 1) end - @tag :no_parity + @tag :no_nethermind @tag :no_geth test "returns true if fetched code from json rpc", %{ json_rpc_named_arguments: json_rpc_named_arguments @@ -5531,7 +5593,7 @@ defmodule Explorer.ChainTest do assert Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments) end - @tag :no_parity + @tag :no_nethermind @tag :no_geth test "returns false if no fetched code from json rpc", %{ json_rpc_named_arguments: json_rpc_named_arguments @@ -5690,6 +5752,66 @@ defmodule Explorer.ChainTest do end end + describe "verified_contracts/2" do + test "without contracts" do + assert [] = Chain.verified_contracts() + end + + test "with contracts" do + %SmartContract{address_hash: hash} = insert(:smart_contract) + + assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts() + end + + test "with contracts can be paginated" do + second_page_contracts_ids = + 50 + |> insert_list(:smart_contract) + |> Enum.map(& &1.id) + + contract = insert(:smart_contract) + + assert second_page_contracts_ids == + [paging_options: %PagingOptions{key: {contract.id}, page_size: 50}] + |> Chain.verified_contracts() + |> Enum.map(& &1.id) + |> Enum.reverse() + end + + test "filters solidity" do + insert(:smart_contract, is_vyper_contract: true) + %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: false) + + assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :solidity) + end + + test "filters vyper" do + insert(:smart_contract, is_vyper_contract: false) + %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: true) + + assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :vyper) + end + + test "search by address" do + insert(:smart_contract) + insert(:smart_contract) + insert(:smart_contract) + %SmartContract{address_hash: hash} = insert(:smart_contract) + + assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: Hash.to_string(hash)) + end + + test "search by name" do + insert(:smart_contract) + insert(:smart_contract) + insert(:smart_contract) + contract_name = "qwertyufhgkhiop" + %SmartContract{address_hash: hash} = insert(:smart_contract, name: contract_name) + + assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: contract_name) + end + end + describe "proxy contracts features" do @proxy_abi [ %{ @@ -5814,26 +5936,36 @@ defmodule Explorer.ChainTest do test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do proxy_contract_address = insert(:contract_address) - assert Chain.combine_proxy_implementation_abi(proxy_contract_address, nil) == [] + + assert Chain.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) == + [] end test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do proxy_contract_address = insert(:contract_address) + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + get_eip1967_implementation() - assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] + assert Chain.combine_proxy_implementation_abi(smart_contract) == [] end test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do proxy_contract_address = insert(:contract_address) - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + assert Chain.combine_proxy_implementation_abi(smart_contract) == @proxy_abi end test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do proxy_contract_address = insert(:contract_address) - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") implementation_contract_address = insert(:contract_address) @@ -5861,7 +5993,7 @@ defmodule Explorer.ChainTest do end ) - combined_abi = Chain.combine_proxy_implementation_abi(proxy_contract_address.hash, @proxy_abi) + combined_abi = Chain.combine_proxy_implementation_abi(smart_contract) assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false @@ -5871,26 +6003,36 @@ defmodule Explorer.ChainTest do test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do proxy_contract_address = insert(:contract_address) - assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, nil) == [] + + assert Chain.get_implementation_abi_from_proxy(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) == + [] end test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do proxy_contract_address = insert(:contract_address) + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + get_eip1967_implementation() - assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] + assert Chain.combine_proxy_implementation_abi(smart_contract) == [] end test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do proxy_contract_address = insert(:contract_address) - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, @proxy_abi) == [] + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + assert Chain.get_implementation_abi_from_proxy(smart_contract) == [] end test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do proxy_contract_address = insert(:contract_address) - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") implementation_contract_address = insert(:contract_address) @@ -5918,14 +6060,16 @@ defmodule Explorer.ChainTest do end ) - implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, @proxy_abi) + implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract) assert implementation_abi == @implementation_abi end test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do proxy_contract_address = insert(:contract_address) - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") implementation_contract_address = insert(:contract_address) @@ -5955,7 +6099,7 @@ defmodule Explorer.ChainTest do end ) - implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, []) + implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract) assert implementation_abi == @implementation_abi end diff --git a/apps/explorer/test/explorer/counters/addresses_tokens_usd_sum_counter_test.exs b/apps/explorer/test/explorer/counters/addresses_tokens_usd_sum_counter_test.exs new file mode 100644 index 000000000000..909757efb5ca --- /dev/null +++ b/apps/explorer/test/explorer/counters/addresses_tokens_usd_sum_counter_test.exs @@ -0,0 +1,34 @@ +defmodule Explorer.Counters.AddressTokenUsdSumTest do + use Explorer.DataCase + + alias Explorer.Counters.AddressTokenUsdSum + + test "populates the cache with the sum of address tokens" do + address = insert(:address) + + address_current_token_balance = + build(:token_balance, + token: build(:token, name: "token name", decimals: Decimal.new(18)) |> Map.put(:usd_value, Decimal.new(10)), + value: Decimal.mult(Decimal.new(100_500), Decimal.from_float(:math.pow(10, 18))) + ) + + address_current_token_balance_2 = + build(:token_balance, + token: build(:token, name: "token name", decimals: Decimal.new(18)) |> Map.put(:usd_value, Decimal.new(10)), + value: Decimal.mult(Decimal.new(100_500), Decimal.from_float(:math.pow(10, 18))) + ) + + AddressTokenUsdSum.fetch(address.hash, [ + {address_current_token_balance, address_current_token_balance.token}, + {address_current_token_balance_2, address_current_token_balance_2.token} + ]) + + Process.sleep(200) + + assert AddressTokenUsdSum.fetch(address.hash, [ + {address_current_token_balance, address_current_token_balance.token}, + {address_current_token_balance_2, address_current_token_balance_2.token} + ]) == + Decimal.new(2_010_000) + end +end diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index 2e6490c78b9e..02cc0ced53cb 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -803,6 +803,7 @@ defmodule Explorer.EtherscanTest do index: internal_transaction.index, transaction_hash: internal_transaction.transaction_hash, type: internal_transaction.type, + call_type: internal_transaction.call_type, gas: internal_transaction.gas, gas_used: internal_transaction.gas_used, error: internal_transaction.error diff --git a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs index 652a727414c3..9f28e1281494 100644 --- a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs @@ -115,7 +115,9 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do {:ok, bypass: bypass} end - test "fetches poa coin id by default", %{bypass: bypass} do + test "fetches poa coin id", %{bypass: bypass} do + Application.put_env(:explorer, :coin, "POA") + Bypass.expect(bypass, "GET", "/coins/list", fn conn -> Conn.resp(conn, 200, @coins_list) end) diff --git a/apps/explorer/test/explorer/graphql_test.exs b/apps/explorer/test/explorer/graphql_test.exs index 2d8ffd1a8fe8..72408bbb07f0 100644 --- a/apps/explorer/test/explorer/graphql_test.exs +++ b/apps/explorer/test/explorer/graphql_test.exs @@ -12,7 +12,7 @@ defmodule Explorer.GraphQLTest do :address |> insert() |> Map.get(:hash) - |> GraphQL.address_to_transactions_query() + |> GraphQL.address_to_transactions_query(:desc) |> Repo.replica().all() assert result == [] @@ -25,7 +25,7 @@ defmodule Explorer.GraphQLTest do [found_transaction] = address_hash - |> GraphQL.address_to_transactions_query() + |> GraphQL.address_to_transactions_query(:desc) |> Repo.replica().all() assert found_transaction.hash == transaction.hash @@ -38,7 +38,7 @@ defmodule Explorer.GraphQLTest do [found_transaction] = address_hash - |> GraphQL.address_to_transactions_query() + |> GraphQL.address_to_transactions_query(:desc) |> Repo.replica().all() assert found_transaction.hash == transaction.hash @@ -51,7 +51,7 @@ defmodule Explorer.GraphQLTest do [found_transaction] = address_hash - |> GraphQL.address_to_transactions_query() + |> GraphQL.address_to_transactions_query(:desc) |> Repo.replica().all() assert found_transaction.hash == transaction.hash @@ -78,7 +78,7 @@ defmodule Explorer.GraphQLTest do found_transactions = address_hash - |> GraphQL.address_to_transactions_query() + |> GraphQL.address_to_transactions_query(:desc) |> Repo.replica().all() block_number_and_index_order = diff --git a/apps/explorer/test/explorer/repo/config_helper_test.exs b/apps/explorer/test/explorer/repo/config_helper_test.exs index a34a75d21be3..8d606fe47cd5 100644 --- a/apps/explorer/test/explorer/repo/config_helper_test.exs +++ b/apps/explorer/test/explorer/repo/config_helper_test.exs @@ -28,6 +28,18 @@ defmodule Explorer.Repo.ConfigHelperTest do assert result[:database] == "test_database" end + test "parse params from database url with hyphen in databasename" do + database_url = "postgresql://test_username:test_password@host-name.test.com:7777/test-database" + + result = ConfigHelper.get_db_config(%{url: database_url, env_func: fn _ -> nil end}) + + assert result[:username] == "test_username" + assert result[:password] == "test_password" + assert result[:hostname] == "host-name.test.com" + assert result[:port] == "7777" + assert result[:database] == "test-database" + end + test "parse params from database url with special characters in password" do database_url = "postgresql://test_username:awN!l#W*g$P%t-l^q&d@hostname.test.com:7777/test_database" diff --git a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs index 3afe5c937f05..9ab3971f08f0 100644 --- a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs +++ b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs @@ -1,6 +1,7 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do use EthereumJSONRPC.Case + alias EthereumJSONRPC.Encoder alias Explorer.Token.InstanceMetadataRetriever alias Plug.Conn @@ -53,7 +54,7 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do ] describe "fetch_metadata/2" do - @tag :no_parity + @tag :no_nethermind @tag :no_geth test "fetches json metadata", %{json_rpc_named_arguments: json_rpc_named_arguments} do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do @@ -166,6 +167,104 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do }) end + test "replace {id} with actual token_id", %{bypass: bypass} do + json = """ + { + "name": "SÊrgio Mendonça {id}" + } + """ + + abi = + [ + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "tokenURI", + "inputs" => [ + %{"type" => "string", "name" => "name", "internalType" => "string"} + ] + } + ] + |> ABI.parse_specification() + |> Enum.at(0) + + encoded_url = + abi + |> Encoder.encode_function_call(["http://localhost:#{bypass.port}/api/card/{id}"]) + |> String.replace("4cf12d26", "") + + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: + "0xc87b56dd0000000000000000000000000000000000000000000000000000000000000309", + to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567" + }, + "latest" + ] + } + ], + _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + error: %{code: -32000, message: "execution reverted"} + } + ]} + end) + |> expect(:json_rpc, fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: + "0x0e89341c0000000000000000000000000000000000000000000000000000000000000309", + to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567" + }, + "latest" + ] + } + ], + _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: encoded_url + } + ]} + end) + + Bypass.expect( + bypass, + "GET", + "/api/card/0000000000000000000000000000000000000000000000000000000000000309", + fn conn -> + Conn.resp(conn, 200, json) + end + ) + + assert {:ok, + %{ + metadata: %{ + "name" => "SÊrgio Mendonça 0000000000000000000000000000000000000000000000000000000000000309" + } + }} == + InstanceMetadataRetriever.fetch_metadata("0x5caebd3b32e210e85ce3e9d51638b9c445481567", 777) + end + test "decodes json file in tokenURI" do data = %{ "c87b56dd" => @@ -187,5 +286,26 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do } }} end + + test "decodes base64 encoded json file in tokenURI" do + data = %{ + "c87b56dd" => + {:ok, + [ + "data:application/json;base64,eyJuYW1lIjogIi54ZGFpIiwgImRlc2NyaXB0aW9uIjogIlB1bmsgRG9tYWlucyBkaWdpdGFsIGlkZW50aXR5LiBWaXNpdCBodHRwczovL3B1bmsuZG9tYWlucy8iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhacFpYZENiM2c5SWpBZ01DQTFNREFnTlRBd0lpQjNhV1IwYUQwaU5UQXdJaUJvWldsbmFIUTlJalV3TUNJK1BHUmxabk0rUEd4cGJtVmhja2R5WVdScFpXNTBJR2xrUFNKbmNtRmtJaUI0TVQwaU1DVWlJSGt4UFNJd0pTSWdlREk5SWpFd01DVWlJSGt5UFNJd0pTSStQSE4wYjNBZ2IyWm1jMlYwUFNJd0pTSWdjM1I1YkdVOUluTjBiM0F0WTI5c2IzSTZjbWRpS0RVNExERTNMREV4TmlrN2MzUnZjQzF2Y0dGamFYUjVPakVpSUM4K1BITjBiM0FnYjJabWMyVjBQU0l4TURBbElpQnpkSGxzWlQwaWMzUnZjQzFqYjJ4dmNqcHlaMklvTVRFMkxESTFMREUzS1R0emRHOXdMVzl3WVdOcGRIazZNU0lnTHo0OEwyeHBibVZoY2tkeVlXUnBaVzUwUGp3dlpHVm1jejQ4Y21WamRDQjRQU0l3SWlCNVBTSXdJaUIzYVdSMGFEMGlOVEF3SWlCb1pXbG5hSFE5SWpVd01DSWdabWxzYkQwaWRYSnNLQ05uY21Ga0tTSXZQangwWlhoMElIZzlJalV3SlNJZ2VUMGlOVEFsSWlCa2IyMXBibUZ1ZEMxaVlYTmxiR2x1WlQwaWJXbGtaR3hsSWlCbWFXeHNQU0ozYUdsMFpTSWdkR1Y0ZEMxaGJtTm9iM0k5SW0xcFpHUnNaU0lnWm05dWRDMXphWHBsUFNKNExXeGhjbWRsSWo0dWVHUmhhVHd2ZEdWNGRENDhkR1Y0ZENCNFBTSTFNQ1VpSUhrOUlqY3dKU0lnWkc5dGFXNWhiblF0WW1GelpXeHBibVU5SW0xcFpHUnNaU0lnWm1sc2JEMGlkMmhwZEdVaUlIUmxlSFF0WVc1amFHOXlQU0p0YVdSa2JHVWlQbkIxYm1zdVpHOXRZV2x1Y3p3dmRHVjRkRDQ4TDNOMlp6ND0ifQ==" + ]} + } + + assert InstanceMetadataRetriever.fetch_json(data) == + {:ok, + %{ + metadata: %{ + "name" => ".xdai", + "description" => "Punk Domains digital identity. Visit https://punk.domains/", + "image" => + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MDAgNTAwIiB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3R5bGU9InN0b3AtY29sb3I6cmdiKDU4LDE3LDExNik7c3RvcC1vcGFjaXR5OjEiIC8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdHlsZT0ic3RvcC1jb2xvcjpyZ2IoMTE2LDI1LDE3KTtzdG9wLW9wYWNpdHk6MSIgLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCIgZmlsbD0idXJsKCNncmFkKSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSJ4LWxhcmdlIj4ueGRhaTwvdGV4dD48dGV4dCB4PSI1MCUiIHk9IjcwJSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPnB1bmsuZG9tYWluczwvdGV4dD48L3N2Zz4=" + } + }} + end end end diff --git a/apps/explorer/test/explorer/token/metadata_retriever_test.exs b/apps/explorer/test/explorer/token/metadata_retriever_test.exs index 9f8ead7468d8..404ae713bb9f 100644 --- a/apps/explorer/test/explorer/token/metadata_retriever_test.exs +++ b/apps/explorer/test/explorer/token/metadata_retriever_test.exs @@ -340,6 +340,58 @@ defmodule Explorer.Token.MetadataRetrieverTest do assert MetadataRetriever.get_functions_of(token.contract_address_hash) == expected end + test "shortens strings larger than 255 characters with unicode graphemes" do + long_token_name_shortened = + "文įĢ ãŽčĢ–æ—¨ã‚„čĻį‚šã‚’įŸ­ããžã¨ã‚ãĻčĄ¨įžã™ã‚‹čĻį´„æ–‡ã€‚å­Ļį”ŸãŽé ƒã€ãƒŦポãƒŧトäŊœæˆãĒおで書いたįĩŒé¨“があるもぎぎ、それäģĨé™ã¯ãžãŖãŸãæ›¸ã„ãĻいãĒいというäēēは多いことでしょう。 しかし、文įĢ " + + token = insert(:token, contract_address: build(:contract_address)) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + 1, + fn requests, _opts -> + {:ok, + Enum.map(requests, fn + %{id: id, method: "eth_call", params: [%{data: "0x313ce567", to: _}, "latest"]} -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000012" + } + + %{id: id, method: "eth_call", params: [%{data: "0x06fdde03", to: _}, "latest"]} -> + %{ + id: id, + result: + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000128e69687e7aba0e381aee8ab96e697a8e38284e8a681e782b9e38292e79fade3818fe381bee381a8e38281e381a6e8a1a8e78fbee38199e3828be8a681e7b484e69687e38082e5ada6e7949fe381aee9a083e38081e383ace3839de383bce38388e4bd9ce68890e381aae381a9e381a7e69bb8e38184e3819fe7b58ce9a893e3818ce38182e3828be38282e381aee381aee38081e3819de3828ce4bba5e9998de381afe381bee381a3e3819fe3818fe69bb8e38184e381a6e38184e381aae38184e381a8e38184e38186e4babae381afe5a49ae38184e38193e381a8e381a7e38197e38287e38186e380822020e38197e3818be38197e38081e69687e7aba0e4bd9ce68890e3818ce88ba6e6898be381aae4babae38284e38081e69687e7aba0e3818ce3828fe3818b000000000000000000000000000000000000000000000000" + } + + %{id: id, method: "eth_call", params: [%{data: "0x95d89b41", to: _}, "latest"]} -> + %{ + id: id, + result: + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003424e540000000000000000000000000000000000000000000000000000000000" + } + + %{id: id, method: "eth_call", params: [%{data: "0x18160ddd", to: _}, "latest"]} -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" + } + end)} + end + ) + + expected = %{ + name: long_token_name_shortened, + decimals: 18, + total_supply: 1_000_000_000_000_000_000, + symbol: "BNT" + } + + assert MetadataRetriever.get_functions_of(token.contract_address_hash) == expected + end + test "retries when some function gave error" do token = insert(:token, contract_address: build(:contract_address)) diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs new file mode 100644 index 000000000000..bdd94920db2c --- /dev/null +++ b/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs @@ -0,0 +1,37 @@ +defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdaterTest do + use Explorer.DataCase, async: false + + alias Explorer.Repo + alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater + alias Explorer.Utility.TokenTransferTokenIdMigratorProgress + + describe "Add range and update last processed block number" do + test "add_range/2" do + TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(2000, true) + LowestBlockNumberUpdater.start_link([]) + + LowestBlockNumberUpdater.add_range(1000, 500) + LowestBlockNumberUpdater.add_range(1500, 1001) + Process.sleep(10) + + assert %{last_processed_block_number: 2000, processed_ranges: [1500..500//-1]} = + :sys.get_state(LowestBlockNumberUpdater) + + assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress) + + LowestBlockNumberUpdater.add_range(499, 300) + LowestBlockNumberUpdater.add_range(299, 0) + Process.sleep(10) + + assert %{last_processed_block_number: 2000, processed_ranges: [1500..0//-1]} = + :sys.get_state(LowestBlockNumberUpdater) + + assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress) + + LowestBlockNumberUpdater.add_range(1999, 1501) + Process.sleep(10) + assert %{last_processed_block_number: 0, processed_ranges: []} = :sys.get_state(LowestBlockNumberUpdater) + assert %{last_processed_block_number: 0} = Repo.one(TokenTransferTokenIdMigratorProgress) + end + end +end diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs new file mode 100644 index 000000000000..8797e90130e6 --- /dev/null +++ b/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs @@ -0,0 +1,31 @@ +defmodule Explorer.TokenTransferTokenIdMigration.WorkerTest do + use Explorer.DataCase, async: false + + alias Explorer.Repo + alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker} + alias Explorer.Utility.TokenTransferTokenIdMigratorProgress + + describe "Move TokenTransfer token_id to token_ids" do + test "Move token_ids and update last processed block number" do + insert(:token_transfer, block_number: 1, token_id: 1, transaction: insert(:transaction)) + insert(:token_transfer, block_number: 500, token_id: 2, transaction: insert(:transaction)) + insert(:token_transfer, block_number: 1000, token_id: 3, transaction: insert(:transaction)) + insert(:token_transfer, block_number: 1500, token_id: 4, transaction: insert(:transaction)) + insert(:token_transfer, block_number: 2000, token_id: 5, transaction: insert(:transaction)) + + TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(3000, true) + LowestBlockNumberUpdater.start_link([]) + + Worker.start_link(idx: 1, first_block: 0, last_block: 3000, step: 2) + Worker.start_link(idx: 2, first_block: 0, last_block: 3000, step: 2) + Worker.start_link(idx: 3, first_block: 0, last_block: 3000, step: 2) + Process.sleep(200) + + token_transfers = Repo.all(Explorer.Chain.TokenTransfer) + assert Enum.all?(token_transfers, fn tt -> is_nil(tt.token_id) end) + + expected_token_ids = [[Decimal.new(1)], [Decimal.new(2)], [Decimal.new(3)], [Decimal.new(4)], [Decimal.new(5)]] + assert ^expected_token_ids = token_transfers |> Enum.map(& &1.token_ids) |> Enum.sort_by(&List.first/1) + end + end +end diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index e12dd24a82d6..da18760983cc 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -34,9 +34,11 @@ defmodule Explorer.DataCase do ExVCR.Config.cassette_library_dir("test/support/fixture/vcr_cassettes") :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo) + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Account) unless tags[:async] do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, {:shared, self()}) end Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 27aee086406f..a181d49ea59b 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -4,10 +4,20 @@ defmodule Explorer.Factory do require Ecto.Query import Ecto.Query + import Explorer.Chain, only: [hash_to_lower_case_string: 1] import Kernel, except: [+: 2] - alias Comeonin.Bcrypt - alias Explorer.Accounts.{User, UserContact} + alias Explorer.Account.{ + Identity, + Watchlist, + WatchlistAddress + } + + alias Explorer.Accounts.{ + User, + UserContact + } + alias Explorer.Admin.Administrator alias Explorer.Chain.Block.{EmissionReward, Range, Reward} @@ -37,6 +47,116 @@ defmodule Explorer.Factory do alias Explorer.Market.MarketHistory alias Explorer.Repo + alias Ueberauth.Strategy.Auth0 + alias Ueberauth.Auth.Info + alias Ueberauth.Auth + + def account_identity_factory do + %Identity{ + uid: sequence("github|"), + email: sequence(:email, &"me-#{&1}@blockscout.com"), + name: sequence("John") + } + end + + def auth_factory do + %Auth{ + info: %Info{ + birthday: nil, + description: nil, + email: sequence(:email, &"test_user-#{&1}@blockscout.com"), + first_name: nil, + image: sequence("https://example.com/avatar/test_user"), + last_name: nil, + location: nil, + name: sequence("User Test"), + nickname: sequence("test_user"), + phone: nil, + urls: %{profile: nil, website: nil} + }, + provider: :auth0, + strategy: Auth0, + uid: sequence("blockscout|000") + } + end + + def watchlist_address_factory do + %{ + "address_hash" => to_string(build(:address).hash), + "name" => sequence("test"), + "notification_settings" => %{ + "native" => %{ + "incoming" => random_bool(), + "outcoming" => random_bool() + }, + "ERC-20" => %{ + "incoming" => random_bool(), + "outcoming" => random_bool() + }, + "ERC-721" => %{ + "incoming" => random_bool(), + "outcoming" => random_bool() + } + }, + "notification_methods" => %{ + "email" => random_bool() + } + } + end + + def custom_abi_factory do + contract_address_hash = to_string(insert(:contract_address).hash) + + %{"contract_address_hash" => contract_address_hash, "name" => sequence("test"), "abi" => contract_code_info().abi} + end + + def public_tags_request_factory do + %{ + "full_name" => sequence("full name"), + "email" => sequence(:email, &"test_user-#{&1}@blockscout.com"), + "tags" => Enum.join(Enum.map(1..Enum.random(1..2), fn _ -> sequence("Tag") end), ";"), + "website" => sequence("website"), + "additional_comment" => sequence("additional_comment"), + "addresses" => Enum.map(1..Enum.random(1..10), fn _ -> to_string(build(:address).hash) end), + "company" => sequence("company"), + "is_owner" => random_bool() + } + end + + def account_watchlist_factory do + %Watchlist{ + identity: build(:account_identity) + } + end + + def tag_address_factory do + %{"name" => sequence("name"), "address_hash" => to_string(build(:address).hash)} + end + + def tag_transaction_factory do + %{"name" => sequence("name"), "transaction_hash" => to_string(insert(:transaction).hash)} + end + + def account_watchlist_address_factory do + hash = build(:address).hash + + %WatchlistAddress{ + name: "wallet", + watchlist: build(:account_watchlist), + address_hash: hash, + address_hash_hash: hash_to_lower_case_string(hash), + watch_coin_input: true, + watch_coin_output: true, + watch_erc_20_input: true, + watch_erc_20_output: true, + watch_erc_721_input: true, + watch_erc_721_output: true, + watch_erc_1155_input: true, + watch_erc_1155_output: true, + notify_email: true + } + end + def address_factory do %Address{ hash: address_hash() @@ -50,6 +170,13 @@ defmodule Explorer.Factory do } end + def unique_address_name_factory do + %Address.Name{ + address: build(:address), + name: sequence("FooContract") + } + end + def unfetched_balance_factory do %CoinBalance{ address_hash: address_hash(), @@ -427,11 +554,7 @@ defmodule Explorer.Factory do end def pending_block_operation_factory do - %PendingBlockOperation{ - # caller MUST supply block - # all operations will default to false - fetch_internal_transactions: false - } + %PendingBlockOperation{} end def internal_transaction_factory() do @@ -522,6 +645,10 @@ defmodule Explorer.Factory do } end + def unique_token_factory do + Map.replace(token_factory(), :name, sequence("Infinite Token")) + end + def token_transfer_log_factory do token_contract_address = build(:address) to_address = build(:address) @@ -676,7 +803,8 @@ defmodule Explorer.Factory do def smart_contract_factory do contract_code_info = contract_code_info() - bytecode_md5 = Helper.contract_code_md5(contract_code_info.bytecode) + {:ok, data} = Explorer.Chain.Data.cast(contract_code_info.bytecode) + bytecode_md5 = Helper.contract_code_md5(data.bytes) %SmartContract{ address_hash: insert(:address, contract_code: contract_code_info.bytecode, verified: true).hash, @@ -689,6 +817,10 @@ defmodule Explorer.Factory do } end + def unique_smart_contract_factory do + Map.replace(smart_contract_factory(), :name, sequence("SimpleStorage")) + end + def decompiled_smart_contract_factory do contract_code_info = contract_code_info() @@ -719,6 +851,15 @@ defmodule Explorer.Factory do } end + def address_coin_balance_factory do + %CoinBalance{ + address: insert(:address), + block_number: insert(:block).number, + value: Enum.random(1..100_000_000), + value_fetched_at: DateTime.utc_now() + } + end + def address_current_token_balance_factory do %CurrentTokenBalance{ address: build(:address), @@ -729,6 +870,17 @@ defmodule Explorer.Factory do } end + def address_current_token_balance_with_token_id_factory do + %CurrentTokenBalance{ + address: build(:address), + token_contract_address_hash: insert(:token).contract_address_hash, + block_number: block_number(), + value: Enum.random(1..100_000), + value_fetched_at: DateTime.utc_now(), + token_id: Enum.random([nil, Enum.random(1..100_000)]) + } + end + defp block_hash_to_next_transaction_index(block_hash) do import Kernel, except: [+: 2] @@ -763,7 +915,7 @@ defmodule Explorer.Factory do %User{ username: username, - password_hash: Bcrypt.hashpwsalt("password"), + password_hash: Bcrypt.hash_pwd_salt("password"), contacts: [ %UserContact{ email: "#{username}@blockscout", @@ -780,4 +932,6 @@ defmodule Explorer.Factory do user: build(:user) } end + + def random_bool, do: Enum.random([true, false]) end diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index 50847130f8ba..418ea17ea08a 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -12,6 +12,7 @@ ExUnit.start() {:ok, _} = Application.ensure_all_started(:ex_machina) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :auto) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :auto) Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) Mox.defmock(Explorer.KnownTokens.Source.TestSource, for: Explorer.KnownTokens.Source) diff --git a/apps/indexer/README.md b/apps/indexer/README.md index 916146622911..57d7ef96f4a5 100644 --- a/apps/indexer/README.md +++ b/apps/indexer/README.md @@ -24,7 +24,6 @@ Some data has to be extracted from already fetched data, and there're several tr - `address_token_balances`: creates token balance entities for futher fetching, based on detected token transfers - `blocks`: extracts block signer hash from additional data for Clique chains - ### Root fetchers - `pending_transaction`: fetches pending transactions (i.e. not yet collated into a block) every second (`pending_transaction_interval`) @@ -32,6 +31,7 @@ Some data has to be extracted from already fetched data, and there're several tr - `block/catchup`: gets unfetched ranges of blocks, imports them in batches Both block fetchers retrieve/extract the blocks themselves and the following additional data: + - `block_second_degree_relations` - `transactions` - `logs` @@ -39,16 +39,19 @@ Both block fetchers retrieve/extract the blocks themselves and the following add - `addresses` The following stubs for further async fetching are inserted as well: + - `block_rewards` - `address_coin_balances` - `address_token_balances` - `tokens` Realtime fetcher also immediately fetches from the node: + - current balances for `addresses` - `address_coin_balances` The following async fetchers are launched for importing missing data: + - `replaced_transaction` - `block_reward` - `uncle_block` @@ -62,6 +65,7 @@ The following async fetchers are launched for importing missing data: These are responsible for fetching additional block data not retrieved in root fetchers. Most of them are based off `BufferedTask`, and the basic algorithm goes like this: + 1. Make an initial streaming request to database to fetch identifiers of all existing unfetched items. 2. Accept new identifiers for fetching via `async_fetch()` method. 3. Split identifier in batches and run tasks on `TaskSupervisor` according to `max_batch_size` and `max_concurrency` settings. @@ -73,13 +77,14 @@ Most of them are based off `BufferedTask`, and the basic algorithm goes like thi - `replaced_transaction`: not a fetcher per se, but rather an async worker, which discards previously pending transactions after they are replaced with new pending transactions with the same nonce, or are collated in a block. - `block_reward`: missing `block_rewards` for consensus blocks - `uncle_block`: blocks for `block_second_degree_relations` with null `uncle_fetched_at` -- `internal_transaction`: for either `blocks` (Parity) or `transactions` with null `internal_transactions_indexed_at` +- `internal_transaction`: for either `blocks` (Nethermind) or `transactions` with null `internal_transactions_indexed_at` - `coin_balance`: for `address_coin_balances` with null `value_fetched_at` - `token_balance`: for `address_token_balances` with null `value_fetched_at`. Also upserts `address_current_token_balances` - `token`: for `tokens` with `cataloged == false` - `contract_code`: for `transactions` with non-null `created_contract_address_hash` and null `created_contract_code_indexed_at` Additionally: + - `token_updater` is run every 2 days to update token metadata - `coin_balance_on_demand` is triggered from web UI to ensure address balance is as up-to-date as possible @@ -106,22 +111,22 @@ This defaults to 150 seconds, but it can be set via adding a configuration to `s ## Testing -### Parity +### Nethermind #### Mox **This is the default setup. `mix test` will work on its own, but to be explicit, use the following setup**: ```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.Mox -mix test --exclude no_parity +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Nethermind.Mox +mix test --exclude no_nethermind ``` #### HTTP / WebSocket ```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket -mix test --exclude no_parity +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Nethermind.HTTPWebSocket +mix test --exclude no_nethermind ``` | Protocol | URL | diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs index 7f6b6f9b7a1e..62d1314f1823 100644 --- a/apps/indexer/config/config.exs +++ b/apps/indexer/config/config.exs @@ -2,85 +2,10 @@ # and its dependencies with the aid of the Config module. import Config -block_transformers = %{ - "clique" => Indexer.Transform.Blocks.Clique, - "base" => Indexer.Transform.Blocks.Base -} - -# Compile time environment variable access requires recompilation. -configured_transformer = System.get_env("BLOCK_TRANSFORMER") || "base" - -block_transformer = - case Map.get(block_transformers, configured_transformer) do - nil -> - raise """ - No such block transformer: #{configured_transformer}. - - Valid values are: - #{Enum.join(Map.keys(block_transformers), "\n")} - - Please update environment variable BLOCK_TRANSFORMER accordingly. - """ - - transformer -> - transformer - end - config :indexer, - block_transformer: block_transformer, - ecto_repos: [Explorer.Repo], - metadata_updater_seconds_interval: - String.to_integer(System.get_env("TOKEN_METADATA_UPDATE_INTERVAL") || "#{2 * 24 * 60 * 60}"), - first_block: System.get_env("FIRST_BLOCK") || "", - last_block: System.get_env("LAST_BLOCK") || "", - trace_first_block: System.get_env("TRACE_FIRST_BLOCK") || "", - trace_last_block: System.get_env("TRACE_LAST_BLOCK") || "", - fetch_rewards_way: System.get_env("FETCH_REWARDS_WAY", "trace_block") - -config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor, - disabled?: - System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu" || - System.get_env("INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER", "false") == "true" - -token_balance_on_demand_fetcher_threshold_minutes = System.get_env("TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES") - -token_balance_on_demand_fetcher_threshold = - case token_balance_on_demand_fetcher_threshold_minutes && - Integer.parse(token_balance_on_demand_fetcher_threshold_minutes) do - {integer, ""} -> integer - _ -> 60 - end - -config :indexer, Indexer.Fetcher.TokenBalanceOnDemand, threshold: token_balance_on_demand_fetcher_threshold - -coin_balance_on_demand_fetcher_threshold_minutes = System.get_env("COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES") - -coin_balance_on_demand_fetcher_threshold = - case coin_balance_on_demand_fetcher_threshold_minutes && - Integer.parse(coin_balance_on_demand_fetcher_threshold_minutes) do - {integer, ""} -> integer - _ -> 60 - end - -config :indexer, Indexer.Fetcher.CoinBalanceOnDemand, threshold: coin_balance_on_demand_fetcher_threshold + ecto_repos: [Explorer.Repo] # config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true -config :indexer, Indexer.Fetcher.BlockReward.Supervisor, - disabled?: System.get_env("INDEXER_DISABLE_BLOCK_REWARD_FETCHER", "false") == "true" - -config :indexer, Indexer.Fetcher.InternalTransaction.Supervisor, - disabled?: System.get_env("INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER", "false") == "true" - -config :indexer, Indexer.Fetcher.CoinBalance.Supervisor, - disabled?: System.get_env("INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER", "false") == "true" - -config :indexer, Indexer.Fetcher.TokenUpdater.Supervisor, - disabled?: System.get_env("INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER", "false") == "true" - -config :indexer, Indexer.Fetcher.EmptyBlocksSanitizer.Supervisor, - disabled?: System.get_env("INDEXER_DISABLE_EMPTY_BLOCK_SANITIZER", "false") == "true" - -config :indexer, Indexer.Supervisor, enabled: System.get_env("DISABLE_INDEXER") != "true" config :indexer, Indexer.Tracer, service: :indexer, diff --git a/apps/indexer/config/dev.exs b/apps/indexer/config/dev.exs index 5e5eab86d9eb..f7f9b205d9f6 100644 --- a/apps/indexer/config/dev.exs +++ b/apps/indexer/config/dev.exs @@ -35,17 +35,3 @@ config :logger, :block_import_timings, level: :debug, path: Path.absname("logs/dev/indexer/block_import_timings.log"), metadata_filter: [fetcher: :block_import_timings] - -variant = - if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do - "ganache" - else - System.get_env("ETHEREUM_JSONRPC_VARIANT") - |> String.split(".") - |> List.last() - |> String.downcase() - end - -# Import variant specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "dev/#{variant}.exs" diff --git a/apps/indexer/config/dev/erigon.exs b/apps/indexer/config/dev/erigon.exs new file mode 100644 index 000000000000..c359d0684b25 --- /dev/null +++ b/apps/indexer/config/dev/erigon.exs @@ -0,0 +1,32 @@ +import Config + +config :indexer, + block_interval: :timer.seconds(5), + json_rpc_named_arguments: [ + transport: + if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", + do: EthereumJSONRPC.HTTP, + else: EthereumJSONRPC.IPC + ), + else: EthereumJSONRPC.IPC, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", + method_to_url: [ + eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" + ], + http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] + ], + variant: EthereumJSONRPC.Erigon + ], + subscribe_named_arguments: [ + transport: + System.get_env("ETHEREUM_JSONRPC_WS_URL") && System.get_env("ETHEREUM_JSONRPC_WS_URL") !== "" && + EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ] + ] diff --git a/apps/indexer/config/dev/geth.exs b/apps/indexer/config/dev/geth.exs index e2f26eca4719..ae3a4b61dc96 100644 --- a/apps/indexer/config/dev/geth.exs +++ b/apps/indexer/config/dev/geth.exs @@ -1,5 +1,14 @@ import Config +hackney_opts_base = [pool: :ethereum_jsonrpc] + +hackney_opts = + if System.get_env("ETHEREUM_JSONRPC_HTTP_INSECURE", "") == "true" do + [:insecure] ++ hackney_opts_base + else + hackney_opts_base + end + config :indexer, block_interval: :timer.seconds(5), json_rpc_named_arguments: [ @@ -11,7 +20,7 @@ config :indexer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", - http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: hackney_opts] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/indexer/config/dev/parity.exs b/apps/indexer/config/dev/nethermind.exs similarity index 96% rename from apps/indexer/config/dev/parity.exs rename to apps/indexer/config/dev/nethermind.exs index b3e14583d753..59b6d96cd089 100644 --- a/apps/indexer/config/dev/parity.exs +++ b/apps/indexer/config/dev/nethermind.exs @@ -19,7 +19,7 @@ config :indexer, ], http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] ], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ], # Example configuration to override json_rpc_named_arguments for just the realtime block fetcher # realtime_overrides: [ @@ -35,7 +35,7 @@ config :indexer, # ], # http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] # ], - # variant: EthereumJSONRPC.Parity + # variant: EthereumJSONRPC.Nethermind # ] # ], subscribe_named_arguments: [ diff --git a/apps/indexer/config/prod.exs b/apps/indexer/config/prod.exs index 03f9e04c8719..7c92ca58535e 100644 --- a/apps/indexer/config/prod.exs +++ b/apps/indexer/config/prod.exs @@ -42,17 +42,3 @@ config :logger, :block_import_timings, path: Path.absname("logs/prod/indexer/block_import_timings.log"), metadata_filter: [fetcher: :block_import_timings], rotate: %{max_bytes: 52_428_800, keep: 19} - -variant = - if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do - "parity" - else - System.get_env("ETHEREUM_JSONRPC_VARIANT") - |> String.split(".") - |> List.last() - |> String.downcase() - end - -# Import variant specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "prod/#{variant}.exs" diff --git a/apps/indexer/config/prod/parity.exs b/apps/indexer/config/prod/erigon.exs similarity index 96% rename from apps/indexer/config/prod/parity.exs rename to apps/indexer/config/prod/erigon.exs index e9dde3481417..0ea612796539 100644 --- a/apps/indexer/config/prod/parity.exs +++ b/apps/indexer/config/prod/erigon.exs @@ -18,7 +18,7 @@ config :indexer, ], http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] ], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Erigon ], subscribe_named_arguments: [ transport: diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs index 4de0919c2be4..076abdcb52ec 100644 --- a/apps/indexer/config/prod/geth.exs +++ b/apps/indexer/config/prod/geth.exs @@ -1,5 +1,14 @@ import Config +hackney_opts_base = [pool: :ethereum_jsonrpc] + +hackney_opts = + if System.get_env("ETHEREUM_JSONRPC_HTTP_INSECURE", "") == "true" do + [:insecure] ++ hackney_opts_base + else + hackney_opts_base + end + config :indexer, block_interval: :timer.seconds(5), json_rpc_named_arguments: [ @@ -11,7 +20,7 @@ config :indexer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), - http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: hackney_opts] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/indexer/config/prod/nethermind.exs b/apps/indexer/config/prod/nethermind.exs new file mode 100644 index 000000000000..bb44f66d5da1 --- /dev/null +++ b/apps/indexer/config/prod/nethermind.exs @@ -0,0 +1,31 @@ +import Config + +config :indexer, + block_interval: :timer.seconds(5), + json_rpc_named_arguments: [ + transport: + if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", + do: EthereumJSONRPC.HTTP, + else: EthereumJSONRPC.IPC + ), + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), + method_to_url: [ + eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") + ], + http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] + ], + variant: EthereumJSONRPC.Nethermind + ], + subscribe_named_arguments: [ + transport: + System.get_env("ETHEREUM_JSONRPC_WS_URL") && System.get_env("ETHEREUM_JSONRPC_WS_URL") !== "" && + EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ] + ] diff --git a/apps/indexer/config/runtime/test.exs b/apps/indexer/config/runtime/test.exs new file mode 100644 index 000000000000..e2043f6c1435 --- /dev/null +++ b/apps/indexer/config/runtime/test.exs @@ -0,0 +1,8 @@ +import Config + +alias EthereumJSONRPC.Variant + +variant = Variant.get() + +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../explorer/config/test") +Code.require_file("#{variant}.exs", "#{__DIR__}/../../../indexer/config/test") diff --git a/apps/indexer/config/test.exs b/apps/indexer/config/test.exs index c1fb6c0c55b0..c789f4f1b218 100644 --- a/apps/indexer/config/test.exs +++ b/apps/indexer/config/test.exs @@ -20,17 +20,3 @@ config :logger, :addresses_without_code, level: :debug, path: Path.absname("logs/test/indexer/addresses_without_code.log"), metadata_filter: [fetcher: :addresses_without_code] - -variant = - if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do - "parity" - else - System.get_env("ETHEREUM_JSONRPC_VARIANT") - |> String.split(".") - |> List.last() - |> String.downcase() - end - -# Import variant specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "test/#{variant}.exs" diff --git a/apps/indexer/config/test/parity.exs b/apps/indexer/config/test/erigon.exs similarity index 78% rename from apps/indexer/config/test/parity.exs rename to apps/indexer/config/test/erigon.exs index 217587522a62..5f15cc7a8531 100644 --- a/apps/indexer/config/test/parity.exs +++ b/apps/indexer/config/test/erigon.exs @@ -4,5 +4,5 @@ config :indexer, json_rpc_named_arguments: [ transport: EthereumJSONRPC.Mox, transport_options: [], - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Erigon ] diff --git a/apps/indexer/config/test/nethermind.exs b/apps/indexer/config/test/nethermind.exs new file mode 100644 index 000000000000..7ef74da67bc9 --- /dev/null +++ b/apps/indexer/config/test/nethermind.exs @@ -0,0 +1,8 @@ +import Config + +config :indexer, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [], + variant: EthereumJSONRPC.Nethermind + ] diff --git a/apps/indexer/lib/indexer/application.ex b/apps/indexer/lib/indexer/application.ex index f261ba8057e5..a65a2660f834 100644 --- a/apps/indexer/lib/indexer/application.ex +++ b/apps/indexer/lib/indexer/application.ex @@ -6,9 +6,13 @@ defmodule Indexer.Application do use Application alias Indexer.Memory + alias Indexer.Prometheus.PendingBlockOperationsCollector + alias Prometheus.Registry @impl Application def start(_type, _args) do + Registry.register_collector(PendingBlockOperationsCollector) + memory_monitor_options = case Application.get_env(:indexer, :memory_limit) do nil -> %{} diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index 012f80325d25..fd8df89d23d9 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -26,65 +26,46 @@ defmodule Indexer.Block.Catchup.Fetcher do alias Indexer.{Block, Tracer} alias Indexer.Block.Catchup.Sequence alias Indexer.Memory.Shrinkable + alias Indexer.Prometheus @behaviour Block.Fetcher - # These are all the *default* values for options. - # DO NOT use them directly in the code. Get options from `state`. - - @blocks_batch_size 10 - @blocks_concurrency 10 @sequence_name :block_catchup_sequencer - defstruct blocks_batch_size: @blocks_batch_size, - blocks_concurrency: @blocks_concurrency, - block_fetcher: nil, + defstruct block_fetcher: nil, memory_monitor: nil - @doc false - def default_blocks_batch_size, do: @blocks_batch_size - @doc """ Required named arguments * `:json_rpc_named_arguments` - `t:EthereumJSONRPC.json_rpc_named_arguments/0` passed to `EthereumJSONRPC.json_rpc/2`. - - The follow options can be overridden: - - * `:blocks_batch_size` - The number of blocks to request in one call to the JSONRPC. Defaults to - `#{@blocks_batch_size}`. Block requests also include the transactions for those blocks. *These transactions - are not paginated.* - * `:blocks_concurrency` - The number of concurrent requests of `:blocks_batch_size` to allow against the JSONRPC. - Defaults to #{@blocks_concurrency}. So, up to `blocks_concurrency * block_batch_size` (defaults to - `#{@blocks_concurrency * @blocks_batch_size}`) blocks can be requested from the JSONRPC at once over all - connections. Up to `block_concurrency * receipts_batch_size * receipts_concurrency` (defaults to - `#{@blocks_concurrency * Block.Fetcher.default_receipts_batch_size() * Block.Fetcher.default_receipts_batch_size()}` - ) receipts can be requested from the JSONRPC at once over all connections. - """ def task( %__MODULE__{ - blocks_batch_size: blocks_batch_size, block_fetcher: %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} } = state ) do Logger.metadata(fetcher: :block_catchup) - with {:ok, latest_block_number} <- fetch_last_block(json_rpc_named_arguments) do - case latest_block_number do - # let realtime indexer get the genesis block - 0 -> + with {:ok, ranges} <- block_ranges(json_rpc_named_arguments) do + case ranges do + # -1 means that latest block is 0, so let realtime indexer get the genesis block + [_..-1] -> %{first_block_number: 0, missing_block_count: 0, last_block_number: 0, shrunk: false} _ -> # realtime indexer gets the current latest block - first = latest_block_number - 1 - last = last_block() + _..first = List.last(ranges) + last.._ = List.first(ranges) Logger.metadata(first_block_number: first, last_block_number: last) - missing_ranges = Chain.missing_block_number_ranges(first..last) + missing_ranges = + ranges + # let it fetch from newest to oldest block + |> Enum.reverse() + |> Enum.flat_map(fn f..l -> Chain.missing_block_number_ranges(l..f) end) range_count = Enum.count(missing_ranges) @@ -93,6 +74,8 @@ defmodule Indexer.Block.Catchup.Fetcher do |> Stream.map(&Enum.count/1) |> Enum.sum() + Prometheus.Instrumenter.missing_blocks(missing_block_count) + Logger.debug(fn -> "Missed blocks in ranges." end, missing_block_range_count: range_count, missing_block_count: missing_block_count @@ -104,7 +87,7 @@ defmodule Indexer.Block.Catchup.Fetcher do false _ -> - step = step(first, last, blocks_batch_size) + step = step(first, last, blocks_batch_size()) sequence_opts = put_memory_monitor([ranges: missing_ranges, step: step], state) gen_server_opts = [name: @sequence_name] {:ok, sequence} = Sequence.start_link(sequence_opts, gen_server_opts) @@ -125,6 +108,27 @@ defmodule Indexer.Block.Catchup.Fetcher do end end + @doc """ + The number of blocks to request in one call to the JSONRPC. Defaults to + 10. Block requests also include the transactions for those blocks. *These transactions + are not paginated. + """ + def blocks_batch_size do + Application.get_env(:indexer, __MODULE__)[:batch_size] + end + + @doc """ + The number of concurrent requests of `blocks_batch_size` to allow against the JSONRPC. + Defaults to 10. So, up to `blocks_concurrency * block_batch_size` (defaults to + `10 * 10`) blocks can be requested from the JSONRPC at once over all + connections. Up to `block_concurrency * receipts_batch_size * receipts_concurrency` (defaults to + `#{10 * Block.Fetcher.default_receipts_batch_size() * Block.Fetcher.default_receipts_concurrency()}` + ) receipts can be requested from the JSONRPC at once over all connections. + """ + def blocks_concurrency do + Application.get_env(:indexer, __MODULE__)[:concurrency] + end + defp fetch_last_block(json_rpc_named_arguments) do case latest_block() do nil -> @@ -177,13 +181,13 @@ defmodule Indexer.Block.Catchup.Fetcher do async_import_token_instances(imported) end - defp stream_fetch_and_import(%__MODULE__{blocks_concurrency: blocks_concurrency} = state, sequence) + defp stream_fetch_and_import(state, sequence) when is_pid(sequence) do sequence |> Sequence.build_stream() |> Task.async_stream( &fetch_and_import_range_from_sequence(state, &1, sequence), - max_concurrency: blocks_concurrency, + max_concurrency: blocks_concurrency(), timeout: :infinity ) |> Stream.run() @@ -202,7 +206,11 @@ defmodule Indexer.Block.Catchup.Fetcher do ) do Logger.metadata(fetcher: :block_catchup, first_block_number: first, last_block_number: last) - case fetch_and_import_range(block_fetcher, range) do + {fetch_duration, result} = :timer.tc(fn -> fetch_and_import_range(block_fetcher, range) end) + + Prometheus.Instrumenter.block_full_process(fetch_duration, __MODULE__) + + case result do {:ok, %{inserted: inserted, errors: errors}} -> errors = cap_seq(sequence, errors) retry(sequence, errors) @@ -210,6 +218,7 @@ defmodule Indexer.Block.Catchup.Fetcher do {:ok, inserted: inserted} {:error, {:import = step, [%Changeset{} | _] = changesets}} = error -> + Prometheus.Instrumenter.import_errors() Logger.error(fn -> ["failed to validate: ", inspect(changesets), ". Retrying."] end, step: step) push_back(sequence, range) @@ -217,6 +226,7 @@ defmodule Indexer.Block.Catchup.Fetcher do error {:error, {:import = step, reason}} = error -> + Prometheus.Instrumenter.import_errors() Logger.error(fn -> [inspect(reason), ". Retrying."] end, step: step) push_back(sequence, range) @@ -347,6 +357,83 @@ defmodule Indexer.Block.Catchup.Fetcher do end end + @doc false + def block_ranges(json_rpc_named_arguments) do + block_ranges_string = Application.get_env(:indexer, :block_ranges) + + ranges = + block_ranges_string + |> String.split(",") + |> Enum.map(fn string_range -> + case String.split(string_range, "..") do + [from_string, "latest"] -> + parse_integer(from_string) + + [from_string, to_string] -> + with {from, ""} <- Integer.parse(from_string), + {to, ""} <- Integer.parse(to_string) do + if from <= to, do: from..to, else: nil + else + _ -> nil + end + + _ -> + nil + end + end) + |> sanitize_ranges() + + case List.last(ranges) do + _from.._to -> + {:ok, ranges} + + nil -> + with {:ok, latest_block_number} <- fetch_last_block(json_rpc_named_arguments) do + {:ok, [last_block()..(latest_block_number - 1)]} + end + + num -> + with {:ok, latest_block_number} <- + EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) do + {:ok, List.update_at(ranges, -1, fn _ -> num..(latest_block_number - 1) end)} + end + end + end + + defp sanitize_ranges(ranges) do + ranges + |> Enum.filter(&(not is_nil(&1))) + |> Enum.sort_by( + fn + from.._to -> from + el -> el + end, + :asc + ) + |> Enum.chunk_while( + nil, + fn + _from.._to = chunk, nil -> + {:cont, chunk} + + _ch_from..ch_to = chunk, acc_from..acc_to = acc -> + if Range.disjoint?(chunk, acc), + do: {:cont, acc, chunk}, + else: {:cont, acc_from..max(ch_to, acc_to)} + + num, nil -> + {:halt, num} + + num, acc_from.._ = acc -> + if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from} + + _, num -> + {:halt, num} + end, + fn reminder -> {:cont, reminder, nil} end + ) + end + defp last_block do string_value = Application.get_env(:indexer, :first_block) @@ -366,8 +453,11 @@ defmodule Indexer.Block.Catchup.Fetcher do defp latest_block do string_value = Application.get_env(:indexer, :last_block) + parse_integer(string_value) + end - case Integer.parse(string_value) do + defp parse_integer(integer_string) do + case Integer.parse(integer_string) do {integer, ""} -> integer _ -> nil end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 8f241dbfaf48..f10bc3b77517 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -29,7 +29,7 @@ defmodule Indexer.Block.Fetcher do UncleBlock } - alias Indexer.Tracer + alias Indexer.{Prometheus, Tracer} alias Indexer.Transform.{ AddressCoinBalances, @@ -121,6 +121,9 @@ defmodule Indexer.Block.Fetcher do _.._ = range ) when callback_module != nil do + {fetch_time, fetched_blocks} = + :timer.tc(fn -> EthereumJSONRPC.fetch_blocks_by_range(range, json_rpc_named_arguments) end) + with {:blocks, {:ok, %Blocks{ @@ -128,7 +131,7 @@ defmodule Indexer.Block.Fetcher do transactions_params: transactions_params_without_receipts, block_second_degree_relations_params: block_second_degree_relations_params, errors: blocks_errors - }}} <- {:blocks, EthereumJSONRPC.fetch_blocks_by_range(range, json_rpc_named_arguments)}, + }}} <- {:blocks, fetched_blocks}, blocks = TransformBlocks.transform_blocks(blocks_params), {:receipts, {:ok, receipt_params}} <- {:receipts, Receipts.fetch(state, transactions_params_without_receipts)}, %{logs: logs, receipts: receipts} = receipt_params, @@ -180,6 +183,7 @@ defmodule Indexer.Block.Fetcher do transactions: %{params: transactions_with_receipts} } ) do + Prometheus.Instrumenter.block_batch_fetch(fetch_time, callback_module) result = {:ok, %{inserted: inserted, errors: blocks_errors}} update_block_cache(inserted[:blocks]) update_transactions_cache(inserted[:transactions]) @@ -231,7 +235,13 @@ defmodule Indexer.Block.Fetcher do } ) - callback_module.import(state, options_with_broadcast) + {import_time, result} = :timer.tc(fn -> callback_module.import(state, options_with_broadcast) end) + + no_blocks_to_import = length(options_with_broadcast.blocks.params) + + import_time_per_import = if no_blocks_to_import != 0, do: import_time / no_blocks_to_import, else: import_time + Prometheus.Instrumenter.block_import(import_time_per_import, callback_module) + result end def async_import_token_instances(%{token_transfers: token_transfers}) do diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 5dacd07a7dc1..6dce096e60e7 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -33,6 +33,7 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Indexer.{Block, Tracer} alias Indexer.Block.Realtime.TaskSupervisor alias Indexer.Fetcher.CoinBalance + alias Indexer.Prometheus alias Indexer.Transform.Addresses alias Timex.Duration @@ -295,6 +296,8 @@ defmodule Indexer.Block.Realtime.Fetcher do {fetch_duration, result} = :timer.tc(fn -> fetch_and_import_range(block_fetcher, block_number_to_fetch..block_number_to_fetch) end) + Prometheus.Instrumenter.block_full_process(fetch_duration, __MODULE__) + case result do {:ok, %{inserted: inserted, errors: []}} -> log_import_timings(inserted, fetch_duration, time_before) @@ -310,6 +313,8 @@ defmodule Indexer.Block.Realtime.Fetcher do end) {:error, {:import = step, [%Changeset{} | _] = changesets}} -> + Prometheus.Instrumenter.import_errors() + params = %{ changesets: changesets, block_number_to_fetch: block_number_to_fetch, @@ -333,6 +338,7 @@ defmodule Indexer.Block.Realtime.Fetcher do end {:error, {:import = step, reason}} -> + Prometheus.Instrumenter.import_errors() Logger.error(fn -> inspect(reason) end, step: step) {:error, {step, reason}} -> @@ -363,6 +369,7 @@ defmodule Indexer.Block.Realtime.Fetcher do defp log_import_timings(%{blocks: [%{number: number, timestamp: timestamp}]}, fetch_duration, time_before) do node_delay = Timex.diff(time_before, timestamp, :seconds) + Prometheus.Instrumenter.node_delay(node_delay) Logger.debug("Block #{number} fetching duration: #{fetch_duration / 1_000_000}s. Node delay: #{node_delay}s.", fetcher: :block_import_timings @@ -375,7 +382,7 @@ defmodule Indexer.Block.Realtime.Fetcher do defp retry_fetch_and_import_block(%{changesets: changesets} = params) do if unknown_block_number_error?(changesets) do - # Wait half a second to give Parity time to sync. + # Wait half a second to give Nethermind time to sync. :timer.sleep(500) number = params.block_number_to_fetch diff --git a/apps/indexer/lib/indexer/fetcher.ex b/apps/indexer/lib/indexer/fetcher.ex index bfd992a98648..fc9851da862e 100644 --- a/apps/indexer/lib/indexer/fetcher.ex +++ b/apps/indexer/lib/indexer/fetcher.ex @@ -22,10 +22,11 @@ defmodule Indexer.Fetcher do fetcher = __MODULE__ supervisor = Keyword.get(opts, :supervisor, Module.concat(fetcher, Supervisor)) task_supervisor = Keyword.get(opts, :task_supervisor, Module.concat(fetcher, TaskSupervisor)) + restart = Keyword.get(opts, :restart, :transient) Module.create( supervisor, - quote bind_quoted: [strategy: strategy, fetcher: fetcher, task_supervisor: task_supervisor] do + quote bind_quoted: [strategy: strategy, fetcher: fetcher, task_supervisor: task_supervisor, restart: restart] do use Supervisor def child_spec([]), do: child_spec([[], []]) @@ -35,7 +36,7 @@ defmodule Indexer.Fetcher do default = %{ id: __MODULE__, start: {__MODULE__, :start_link, start_link_arguments}, - restart: :transient, + restart: unquote(restart), type: :supervisor } diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex index d44a7e1f71ae..c473aa00f052 100644 --- a/apps/indexer/lib/indexer/fetcher/block_reward.ex +++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex @@ -6,7 +6,7 @@ defmodule Indexer.Fetcher.BlockReward do retrieved from the database and compared against that returned from `EthereumJSONRPC.` """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance.ex b/apps/indexer/lib/indexer/fetcher/coin_balance.ex index 7b175c7915b3..de0e03e1f6d7 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance.ex @@ -4,7 +4,7 @@ defmodule Indexer.Fetcher.CoinBalance do `fetched_coin_balance_block_number` to value at max `t:Explorer.Chain.Address.CoinBalance.t/0` `block_number` for the given `t:Explorer.Chain.Address.t/` `hash`. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger @@ -20,13 +20,8 @@ defmodule Indexer.Fetcher.CoinBalance do @behaviour BufferedTask - @defaults [ - flush_interval: :timer.seconds(3), - max_batch_size: 500, - max_concurrency: 4, - task_supervisor: Indexer.Fetcher.CoinBalance.TaskSupervisor, - metadata: [fetcher: :coin_balance] - ] + @default_max_batch_size 500 + @default_max_concurrency 4 @doc """ Asynchronously fetches balances for each address `hash` at the `block_number`. @@ -56,7 +51,7 @@ defmodule Indexer.Fetcher.CoinBalance do end merged_init_options = - @defaults + defaults() |> Keyword.merge(mergeable_init_options) |> Keyword.put(:state, state) @@ -264,4 +259,14 @@ defmodule Indexer.Fetcher.CoinBalance do end end) end + + defp defaults do + [ + flush_interval: :timer.seconds(3), + max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, + max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, + task_supervisor: Indexer.Fetcher.CoinBalance.TaskSupervisor, + metadata: [fetcher: :coin_balance] + ] + end end diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex index ac02a8f2d288..9813047e3921 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex @@ -8,7 +8,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do """ use GenServer - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent import Ecto.Query, only: [from: 2] import EthereumJSONRPC, only: [integer_to_quantity: 1] @@ -64,10 +64,12 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do GenServer.start_link(__MODULE__, json_rpc_named_arguments, server_opts) end + @impl true def init(json_rpc_named_arguments) do {:ok, %{json_rpc_named_arguments: json_rpc_named_arguments}} end + @impl true def handle_cast({:fetch_and_update, block_number, address}, state) do result = fetch_and_update(block_number, address, state.json_rpc_named_arguments) @@ -78,18 +80,30 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do {:noreply, state} end + @impl true def handle_cast({:fetch_and_import, block_number, address}, state) do fetch_and_import(block_number, address, state.json_rpc_named_arguments) {:noreply, state} end + @impl true def handle_cast({:fetch_and_import_daily_balances, block_number, address}, state) do fetch_and_import_daily_balances(block_number, address, state.json_rpc_named_arguments) {:noreply, state} end + @impl true + def handle_info({:DOWN, _, :process, _, _}, state) do + {:noreply, state} + end + + @impl true + def handle_info({_ref, _}, state) do + {:noreply, state} + end + ## Implementation defp do_trigger_fetch(%Address{fetched_coin_balance_block_number: nil} = address, latest_block_number, _) do diff --git a/apps/indexer/lib/indexer/fetcher/contract_code.ex b/apps/indexer/lib/indexer/fetcher/contract_code.ex index 8f1247410761..908865287bf6 100644 --- a/apps/indexer/lib/indexer/fetcher/contract_code.ex +++ b/apps/indexer/lib/indexer/fetcher/contract_code.ex @@ -3,7 +3,7 @@ defmodule Indexer.Fetcher.ContractCode do Fetches `contract_code` `t:Explorer.Chain.Address.t/0`. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger diff --git a/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex b/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex index d70c5496fffd..06d02264791e 100644 --- a/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex +++ b/apps/indexer/lib/indexer/fetcher/empty_blocks_sanitizer.ex @@ -5,7 +5,7 @@ defmodule Indexer.Fetcher.EmptyBlocksSanitizer do """ use GenServer - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent require Logger diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index 13716a88c414..1e53314e8a4b 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -5,7 +5,7 @@ defmodule Indexer.Fetcher.InternalTransaction do See `async_fetch/1` for details on configuring limits. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger @@ -21,16 +21,8 @@ defmodule Indexer.Fetcher.InternalTransaction do @behaviour BufferedTask - @max_batch_size 10 - @max_concurrency 4 - @defaults [ - flush_interval: :timer.seconds(3), - max_concurrency: @max_concurrency, - max_batch_size: @max_batch_size, - poll: true, - task_supervisor: Indexer.Fetcher.InternalTransaction.TaskSupervisor, - metadata: [fetcher: :internal_transaction] - ] + @default_max_batch_size 10 + @default_max_concurrency 4 @doc """ Asynchronously fetches internal transactions. @@ -40,10 +32,10 @@ defmodule Indexer.Fetcher.InternalTransaction do Internal transactions are an expensive upstream operation. The number of results to fetch is configured by `@max_batch_size` and represents the number of transaction hashes to request internal transactions in a single JSONRPC - request. Defaults to `#{@max_batch_size}`. + request. Defaults to `#{@default_max_batch_size}`. The `@max_concurrency` attribute configures the number of concurrent requests - of `@max_batch_size` to allow against the JSONRPC. Defaults to `#{@max_concurrency}`. + of `@max_batch_size` to allow against the JSONRPC. Defaults to `#{@default_max_concurrency}`. *Note*: The internal transactions for individual transactions cannot be paginated, so the total number of internal transactions that could be produced is unknown. @@ -68,7 +60,7 @@ defmodule Indexer.Fetcher.InternalTransaction do end merged_init_opts = - @defaults + defaults() |> Keyword.merge(mergeable_init_options) |> Keyword.put(:state, state) @@ -108,7 +100,10 @@ defmodule Indexer.Fetcher.InternalTransaction do json_rpc_named_arguments |> Keyword.fetch!(:variant) |> case do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> + EthereumJSONRPC.fetch_block_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) + + EthereumJSONRPC.Erigon -> EthereumJSONRPC.fetch_block_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) EthereumJSONRPC.Besu -> @@ -124,7 +119,7 @@ defmodule Indexer.Fetcher.InternalTransaction do end |> case do {:ok, internal_transactions_params} -> - import_internal_transaction(internal_transactions_params, filtered_unique_numbers) + safe_import_internal_transaction(internal_transactions_params, filtered_unique_numbers) {:error, reason} -> Logger.error(fn -> ["failed to fetch internal transactions for blocks: ", inspect(reason)] end, @@ -188,6 +183,14 @@ defmodule Indexer.Fetcher.InternalTransaction do end) end + defp safe_import_internal_transaction(internal_transactions_params, block_numbers) do + import_internal_transaction(internal_transactions_params, block_numbers) + rescue + Postgrex.Error -> + handle_foreign_key_violation(internal_transactions_params, block_numbers) + {:retry, block_numbers} + end + defp import_internal_transaction(internal_transactions_params, unique_numbers) do internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params) @@ -270,4 +273,31 @@ defmodule Indexer.Fetcher.InternalTransaction do end end) end + + defp handle_foreign_key_violation(internal_transactions_params, block_numbers) do + Chain.remove_blocks_consensus(block_numbers) + + transaction_hashes = + internal_transactions_params + |> Enum.map(&to_string(&1.transaction_hash)) + |> Enum.uniq() + + Logger.error(fn -> + [ + "foreign_key_violation on internal transactions import, foreign transactions hashes: ", + Enum.join(transaction_hashes, ", ") + ] + end) + end + + defp defaults do + [ + flush_interval: :timer.seconds(3), + max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, + max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, + poll: true, + task_supervisor: Indexer.Fetcher.InternalTransaction.TaskSupervisor, + metadata: [fetcher: :internal_transaction] + ] + end end diff --git a/apps/indexer/lib/indexer/fetcher/pending_transaction.ex b/apps/indexer/lib/indexer/fetcher/pending_transaction.ex index 907fd74a5e81..946ce0ea28a9 100644 --- a/apps/indexer/lib/indexer/fetcher/pending_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/pending_transaction.ex @@ -6,7 +6,7 @@ defmodule Indexer.Fetcher.PendingTransaction do validated version that may make it to the database first. """ use GenServer - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent require Logger diff --git a/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex b/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex index 26585cc9aa47..0e0aa33c9717 100644 --- a/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/replaced_transaction.ex @@ -3,7 +3,7 @@ defmodule Indexer.Fetcher.ReplacedTransaction do Finds and updates replaced transactions. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger diff --git a/apps/indexer/lib/indexer/fetcher/token.ex b/apps/indexer/lib/indexer/fetcher/token.ex index 8c924e2ad245..3dc3d3dad477 100644 --- a/apps/indexer/lib/indexer/fetcher/token.ex +++ b/apps/indexer/lib/indexer/fetcher/token.ex @@ -3,7 +3,7 @@ defmodule Indexer.Fetcher.Token do Fetches information about a token. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators alias Explorer.Chain diff --git a/apps/indexer/lib/indexer/fetcher/token_balance.ex b/apps/indexer/lib/indexer/fetcher/token_balance.ex index b23978facd60..0da2c471f663 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance.ex @@ -13,7 +13,7 @@ defmodule Indexer.Fetcher.TokenBalance do that always raise errors interacting with the Smart Contract. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger diff --git a/apps/indexer/lib/indexer/fetcher/token_instance.ex b/apps/indexer/lib/indexer/fetcher/token_instance.ex index 15d653f31b4e..ac4f8499b6d0 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance.ex @@ -3,7 +3,7 @@ defmodule Indexer.Fetcher.TokenInstance do Fetches information about a token instance. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger @@ -50,7 +50,13 @@ defmodule Indexer.Fetcher.TokenInstance do end @impl BufferedTask - def run([%{contract_address_hash: token_contract_address_hash, token_id: token_id}], _json_rpc_named_arguments) do + def run([%{contract_address_hash: hash, token_id: token_id}], _json_rpc_named_arguments) do + fetch_instance(hash, token_id) + + :ok + end + + defp fetch_instance(token_contract_address_hash, token_id) do case InstanceMetadataRetriever.fetch_metadata(to_string(token_contract_address_hash), Decimal.to_integer(token_id)) do {:ok, %{metadata: metadata}} -> params = %{ @@ -82,26 +88,36 @@ defmodule Indexer.Fetcher.TokenInstance do :ok end - - :ok end @doc """ Fetches token instance data asynchronously. """ - def async_fetch(token_transfers) when is_list(token_transfers) do + def async_fetch(data) do + async_fetch(data, __MODULE__.Supervisor.disabled?()) + end + + def async_fetch(_data, true), do: :ok + + def async_fetch(token_transfers, _disabled?) when is_list(token_transfers) do data = token_transfers - |> Enum.reject(fn token_transfer -> is_nil(token_transfer.token_id) end) + |> Enum.reject(fn token_transfer -> is_nil(token_transfer.token_ids) end) |> Enum.map(fn token_transfer -> - %{contract_address_hash: token_transfer.token_contract_address_hash, token_id: token_transfer.token_id} + Enum.map(token_transfer.token_ids, fn token_id -> + %{ + contract_address_hash: token_transfer.token_contract_address_hash, + token_id: token_id + } + end) end) + |> List.flatten() |> Enum.uniq() BufferedTask.buffer(__MODULE__, data) end - def async_fetch(data) do + def async_fetch(data, _disabled?) do BufferedTask.buffer(__MODULE__, data) end end diff --git a/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex index 934e936afa56..bff276c82902 100644 --- a/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex @@ -5,7 +5,7 @@ defmodule Indexer.Fetcher.TokenTotalSupplyOnDemand do """ use GenServer - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent alias Explorer.{Chain, Repo} alias Explorer.Chain.{Address, Token} diff --git a/apps/indexer/lib/indexer/fetcher/token_updater.ex b/apps/indexer/lib/indexer/fetcher/token_updater.ex index 9a685b61d11c..fec2df8d5927 100644 --- a/apps/indexer/lib/indexer/fetcher/token_updater.ex +++ b/apps/indexer/lib/indexer/fetcher/token_updater.ex @@ -2,7 +2,7 @@ defmodule Indexer.Fetcher.TokenUpdater do @moduledoc """ Updates metadata for cataloged tokens """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent require Logger diff --git a/apps/indexer/lib/indexer/fetcher/uncle_block.ex b/apps/indexer/lib/indexer/fetcher/uncle_block.ex index a672711562b3..f9533b8a97fc 100644 --- a/apps/indexer/lib/indexer/fetcher/uncle_block.ex +++ b/apps/indexer/lib/indexer/fetcher/uncle_block.ex @@ -4,7 +4,7 @@ defmodule Indexer.Fetcher.UncleBlock do `uncle_fetched_at` where the `uncle_hash` matches `hash`. """ - use Indexer.Fetcher + use Indexer.Fetcher, restart: :permanent use Spandex.Decorators require Logger diff --git a/apps/indexer/lib/indexer/prometheus/instrumenter.ex b/apps/indexer/lib/indexer/prometheus/instrumenter.ex new file mode 100644 index 000000000000..d9dfda5dd8a3 --- /dev/null +++ b/apps/indexer/lib/indexer/prometheus/instrumenter.ex @@ -0,0 +1,61 @@ +defmodule Indexer.Prometheus.Instrumenter do + @moduledoc """ + Blocks fetch and import metrics for `Prometheus`. + """ + + use Prometheus.Metric + + @histogram [ + name: :block_full_processing_duration_microseconds, + labels: [:fetcher], + buckets: [1000, 5000, 10000, 100_000], + duration_unit: :microseconds, + help: "Block whole processing time including fetch and import" + ] + + @histogram [ + name: :block_import_duration_microseconds, + labels: [:fetcher], + buckets: [1000, 5000, 10000, 100_000], + duration_unit: :microseconds, + help: "Block import time" + ] + + @histogram [ + name: :block_batch_fetch_request_duration_microseconds, + labels: [:fetcher], + buckets: [1000, 5000, 10000, 100_000], + duration_unit: :microseconds, + help: "Block fetch batch request processing time" + ] + + @gauge [name: :missing_block_count, help: "Number of missing blocks in the database"] + + @gauge [name: :delay_from_last_node_block, help: "Delay from the last block on the node in seconds"] + + @counter [name: :import_errors_count, help: "Number of database import errors"] + + def block_full_process(time, fetcher) do + Histogram.observe([name: :block_full_processing_duration_microseconds, labels: [fetcher]], time) + end + + def block_import(time, fetcher) do + Histogram.observe([name: :block_import_duration_microseconds, labels: [fetcher]], time) + end + + def block_batch_fetch(time, fetcher) do + Histogram.observe([name: :block_batch_fetch_request_duration_microseconds, labels: [fetcher]], time) + end + + def missing_blocks(missing_block_count) do + Gauge.set([name: :missing_block_count], missing_block_count) + end + + def node_delay(delay) do + Gauge.set([name: :delay_from_last_node_block], delay) + end + + def import_errors(error_count \\ 1) do + Counter.inc([name: :import_errors_count], error_count) + end +end diff --git a/apps/indexer/lib/indexer/prometheus/pending_block_operations_collector.ex b/apps/indexer/lib/indexer/prometheus/pending_block_operations_collector.ex new file mode 100644 index 000000000000..e1e836a6bcfa --- /dev/null +++ b/apps/indexer/lib/indexer/prometheus/pending_block_operations_collector.ex @@ -0,0 +1,29 @@ +defmodule Indexer.Prometheus.PendingBlockOperationsCollector do + @moduledoc """ + Custom collector to count number of records in pending_block_operations table. + """ + + use Prometheus.Collector + + alias Explorer.Chain.PendingBlockOperation + alias Explorer.Repo + alias Prometheus.Model + + def collect_mf(_registry, callback) do + callback.( + create_gauge( + :pending_block_operations_count, + "Number of records in pending_block_operations table", + Repo.aggregate(PendingBlockOperation, :count) + ) + ) + end + + def collect_metrics(:pending_block_operations_count, count) do + Model.gauge_metrics([{count}]) + end + + defp create_gauge(name, help, data) do + Model.create_mf(name, help, :gauge, __MODULE__, data) + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 1636bedcda33..1980271c3465 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -92,52 +92,68 @@ defmodule Indexer.Supervisor do realtime_subscribe_named_arguments = realtime_overrides[:subscribe_named_arguments] || subscribe_named_arguments - basic_fetchers = [ - # Root fetchers - {PendingTransaction.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, - {Realtime.Supervisor, - [ - %{block_fetcher: realtime_block_fetcher, subscribe_named_arguments: realtime_subscribe_named_arguments}, - [name: Realtime.Supervisor] - ]}, - {Catchup.Supervisor, - [ - %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor}, - [name: Catchup.Supervisor] - ]}, - - # Async catchup fetchers - {UncleBlock.Supervisor, [[block_fetcher: block_fetcher, memory_monitor: memory_monitor]]}, - {BlockReward.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {InternalTransaction.Supervisor, - [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {CoinBalance.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {Token.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {TokenInstance.Supervisor, - [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {ContractCode.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {TokenBalance.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {TokenUpdater.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, - - # Out-of-band fetchers - {CoinBalanceOnDemand.Supervisor, [json_rpc_named_arguments]}, - {EmptyBlocksSanitizer.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, - {TokenTotalSupplyOnDemand.Supervisor, [json_rpc_named_arguments]}, - {PendingTransactionsSanitizer, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, - - # Temporary workers - {UncatalogedTokenTransfers.Supervisor, [[]]}, - {UnclesWithoutIndex.Supervisor, - [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {BlocksTransactionsMismatch.Supervisor, - [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {PendingOpsCleaner, [[], []]} - ] + basic_fetchers = + [ + # Root fetchers + {PendingTransaction.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, + configure(Realtime.Supervisor, [ + %{block_fetcher: realtime_block_fetcher, subscribe_named_arguments: realtime_subscribe_named_arguments}, + [name: Realtime.Supervisor] + ]), + {Catchup.Supervisor, + [ + %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor}, + [name: Catchup.Supervisor] + ]}, + + # Async catchup fetchers + {UncleBlock.Supervisor, [[block_fetcher: block_fetcher, memory_monitor: memory_monitor]]}, + {BlockReward.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {InternalTransaction.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {CoinBalance.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {Token.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {TokenInstance.Supervisor, + [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]}, + {ContractCode.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {TokenBalance.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {TokenUpdater.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, + + # Out-of-band fetchers + {CoinBalanceOnDemand.Supervisor, [json_rpc_named_arguments]}, + {EmptyBlocksSanitizer.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, + {TokenTotalSupplyOnDemand.Supervisor, [json_rpc_named_arguments]}, + {PendingTransactionsSanitizer, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, + + # Temporary workers + {UncatalogedTokenTransfers.Supervisor, [[]]}, + {UnclesWithoutIndex.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {BlocksTransactionsMismatch.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {PendingOpsCleaner, [[], []]} + ] + |> List.flatten() Supervisor.init( basic_fetchers, strategy: :one_for_one ) end + + defp configure(process, opts) do + if Application.get_env(:indexer, process)[:enabled] do + [{process, opts}] + else + [] + end + end end diff --git a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex index 8b0d62628bba..32a8b2f1ff5b 100644 --- a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex +++ b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex @@ -37,7 +37,7 @@ defmodule Indexer.Temporary.UncatalogedTokenTransfers do def init(opts) do sup_pid = Keyword.fetch!(opts, :supervisor) - retry_interval = Keyword.get(opts, :retry_interval, 10_000) + retry_interval = Keyword.get(opts, :retry_interval, 300) send(self(), :scan) diff --git a/apps/indexer/lib/indexer/transform/address_token_balances.ex b/apps/indexer/lib/indexer/transform/address_token_balances.ex index ab6bb02fee8d..3dc7b5be525d 100644 --- a/apps/indexer/lib/indexer/transform/address_token_balances.ex +++ b/apps/indexer/lib/indexer/transform/address_token_balances.ex @@ -11,30 +11,23 @@ defmodule Indexer.Transform.AddressTokenBalances do defp reducer({:token_transfers_params, token_transfers_params}, initial) when is_list(token_transfers_params) do token_transfers_params - |> ignore_burn_address_transfers_for_token_erc_721 + |> ignore_burn_address_transfers_for_token_erc_721() |> Enum.reduce(initial, fn %{ block_number: block_number, from_address_hash: from_address_hash, to_address_hash: to_address_hash, token_contract_address_hash: token_contract_address_hash, - token_id: token_id, + token_ids: token_ids, token_type: token_type - } = params, + }, acc when is_integer(block_number) and is_binary(from_address_hash) and is_binary(to_address_hash) and is_binary(token_contract_address_hash) -> - if params[:token_ids] && token_type == "ERC-1155" do - params[:token_ids] - |> Enum.reduce(acc, fn id, sub_acc -> - sub_acc - |> add_token_balance_address(from_address_hash, token_contract_address_hash, id, token_type, block_number) - |> add_token_balance_address(to_address_hash, token_contract_address_hash, id, token_type, block_number) - end) - else - acc - |> add_token_balance_address(from_address_hash, token_contract_address_hash, token_id, token_type, block_number) - |> add_token_balance_address(to_address_hash, token_contract_address_hash, token_id, token_type, block_number) - end + Enum.reduce(token_ids || [nil], acc, fn id, sub_acc -> + sub_acc + |> add_token_balance_address(from_address_hash, token_contract_address_hash, id, token_type, block_number) + |> add_token_balance_address(to_address_hash, token_contract_address_hash, id, token_type, block_number) + end) end) end diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 22638dbc130a..77f005e92261 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -86,7 +86,7 @@ defmodule Indexer.Transform.TokenTransfers do to_address_hash: truncate_address_hash(log.third_topic), token_contract_address_hash: log.address_hash, transaction_hash: log.transaction_hash, - token_id: nil, + token_ids: nil, token_type: "ERC-20" } @@ -110,7 +110,7 @@ defmodule Indexer.Transform.TokenTransfers do from_address_hash: truncate_address_hash(log.second_topic), to_address_hash: truncate_address_hash(log.third_topic), token_contract_address_hash: log.address_hash, - token_id: token_id || 0, + token_ids: [token_id || 0], transaction_hash: log.transaction_hash, token_type: "ERC-721" } @@ -142,7 +142,7 @@ defmodule Indexer.Transform.TokenTransfers do from_address_hash: encode_address_hash(from_address_hash), to_address_hash: encode_address_hash(to_address_hash), token_contract_address_hash: log.address_hash, - token_id: token_id, + token_ids: [token_id], transaction_hash: log.transaction_hash, token_type: "ERC-721" } @@ -199,7 +199,6 @@ defmodule Indexer.Transform.TokenTransfers do transaction_hash: log.transaction_hash, token_type: "ERC-1155", token_ids: token_ids, - token_id: nil, amounts: values } @@ -224,7 +223,7 @@ defmodule Indexer.Transform.TokenTransfers do token_contract_address_hash: log.address_hash, transaction_hash: log.transaction_hash, token_type: "ERC-1155", - token_id: token_id + token_ids: [token_id] } token = %{ diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 9be8318b8ec4..0aaa713418dc 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -10,11 +10,11 @@ defmodule Indexer.MixProject do deps: deps(), deps_path: "../../deps", description: "Fetches block chain data from on-chain node for later reading with Explorer.", - elixir: "~> 1.10", + elixir: "~> 1.13", elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "0.1.0" + version: "4.1.8" ] end @@ -39,7 +39,7 @@ defmodule Indexer.MixProject do [ # Optional dependency of `:spandex` for `Spandex.Decorators` {:decorator, "~> 1.4"}, - # JSONRPC access to Parity for `Explorer.Indexer` + # JSONRPC access to Nethermind for `Explorer.Indexer` {:ethereum_jsonrpc, in_umbrella: true}, # RLP encoding {:ex_rlp, "~> 0.5.2"}, @@ -50,7 +50,8 @@ defmodule Indexer.MixProject do # Log errors and application output to separate files {:logger_file_backend, "~> 0.0.10"}, # Mocking `EthereumJSONRPC.Transport`, so we avoid hitting real chains for local testing - {:mox, "~> 0.4", only: [:test]}, + {:mox, "~> 1.0", only: [:test]}, + {:prometheus_ex, git: "https://github.com/lanodan/prometheus.ex", branch: "fix/elixir-1.14", override: true}, # Tracing {:spandex, "~> 3.0"}, # `:spandex` integration with Datadog diff --git a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs index dca037a9cbcf..9519e8e25ffc 100644 --- a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs +++ b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs @@ -37,7 +37,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> block_number = 3_416_888 block_quantity = integer_to_quantity(block_number) @@ -207,7 +207,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do {:ok, latest_block_number} = EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) - default_blocks_batch_size = Catchup.Fetcher.default_blocks_batch_size() + default_blocks_batch_size = Catchup.Fetcher.blocks_batch_size() assert latest_block_number > default_blocks_batch_size @@ -492,7 +492,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do end) |> (fn mock -> case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> expect(mock, :json_rpc, fn [%{method: "trace_block"} | _] = requests, _options -> {:ok, Enum.map(requests, fn %{id: id} -> %{id: id, result: []} end)} end) diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index 0a77c3a93b15..dc8e6ebaee3b 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -27,7 +27,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do transport: EthereumJSONRPC.Mox, transport_options: [], # Which one does not matter, so pick one - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ] } end @@ -126,6 +126,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do test "ignores fetched beneficiaries with different hash for same number", %{ json_rpc_named_arguments: json_rpc_named_arguments } do + Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -264,7 +265,6 @@ defmodule Indexer.Block.Catchup.FetcherTest do assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} = Fetcher.task(%Fetcher{ - blocks_batch_size: 1, block_fetcher: %Block.Fetcher{ callback_module: Fetcher, json_rpc_named_arguments: json_rpc_named_arguments @@ -280,6 +280,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do test "async fetches beneficiaries when individual responses error out", %{ json_rpc_named_arguments: json_rpc_named_arguments } do + Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -413,7 +414,6 @@ defmodule Indexer.Block.Catchup.FetcherTest do assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} = Fetcher.task(%Fetcher{ - blocks_batch_size: 1, block_fetcher: %Block.Fetcher{ callback_module: Fetcher, json_rpc_named_arguments: json_rpc_named_arguments @@ -431,6 +431,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do test "async fetches beneficiaries when entire call errors out", %{ json_rpc_named_arguments: json_rpc_named_arguments } do + Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -557,7 +558,6 @@ defmodule Indexer.Block.Catchup.FetcherTest do assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} = Fetcher.task(%Fetcher{ - blocks_batch_size: 1, block_fetcher: %Block.Fetcher{ callback_module: Fetcher, json_rpc_named_arguments: json_rpc_named_arguments @@ -572,6 +572,57 @@ defmodule Indexer.Block.Catchup.FetcherTest do end end + describe "block_ranges/0" do + setup do + initial_env = Application.get_all_env(:indexer) + on_exit(fn -> Application.put_all_env([{:indexer, initial_env}]) end) + end + + test "ignores bad ranges", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{method: "eth_getBlockByNumber", params: ["latest", false]}, _options -> + {:ok, %{"number" => "0x100"}} + end) + + # doing such workaround is safe since this module is not async + Application.put_env(:indexer, :block_ranges, "1..5,3..5,2qw1..12,10..11a,,asd..qwe,10..latest") + # latest block is left for realtime_index + assert Fetcher.block_ranges(json_rpc_named_arguments) == {:ok, [1..5, 10..255]} + end + + test "ignores FIRST_BLOCK/LAST_BLOCK when BLOCK_RANGES defined", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + Application.put_env(:indexer, :first_block, "1") + Application.put_env(:indexer, :last_block, "10") + Application.put_env(:indexer, :block_ranges, "2..5,10..100") + assert Fetcher.block_ranges(json_rpc_named_arguments) == {:ok, [2..5, 10..100]} + end + + test "uses FIRST_BLOCK/LAST_BLOCK when BLOCK_RANGES is undefined or invalid", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + Application.put_env(:indexer, :first_block, "1") + Application.put_env(:indexer, :last_block, "10") + assert Fetcher.block_ranges(json_rpc_named_arguments) == {:ok, [1..9]} + + Application.put_env(:indexer, :block_ranges, "latest..123,,fvdskvjglav!@#$%^&,2..1") + assert Fetcher.block_ranges(json_rpc_named_arguments) == {:ok, [1..9]} + end + + test "all ranges are disjoint", %{json_rpc_named_arguments: json_rpc_named_arguments} do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{method: "eth_getBlockByNumber", params: ["latest", false]}, _options -> + {:ok, %{"number" => "0x100"}} + end) + + Application.put_env(:indexer, :block_ranges, "10..20,5..15,18..25,35..40,30..50,100..latest,150..200") + assert Fetcher.block_ranges(json_rpc_named_arguments) == {:ok, [5..25, 30..50, 100..255]} + end + end + defp count(schema) do Repo.one!(select(schema, fragment("COUNT(*)"))) end diff --git a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs index 95570db4a232..c70d10761a01 100644 --- a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs +++ b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs @@ -22,7 +22,7 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do } end - @tag :no_parity + @tag :no_nethermind @tag :no_geth test "fetches logs setting their blocks if they're null", %{ block_fetcher: %Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} = block_fetcher diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 82b11efe32d6..24be0dfa8803 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -82,7 +82,7 @@ defmodule Indexer.Block.FetcherTest do # res = eth_block_number_fake_response(block_quantity) # case Keyword.fetch!(json_rpc_named_arguments, :variant) do - # EthereumJSONRPC.Parity -> + # EthereumJSONRPC.Nethermind -> # EthereumJSONRPC.Mox # |> expect(:json_rpc, fn [%{id: id, method: "eth_getBlockByNumber", params: [^block_quantity, true]}], # _options -> @@ -219,7 +219,7 @@ defmodule Indexer.Block.FetcherTest do # } # } - # EthereumJSONRPC.Parity -> + # EthereumJSONRPC.Nethermind -> # %{ # address_hash: %Explorer.Chain.Hash{ # byte_count: 20, @@ -273,7 +273,7 @@ defmodule Indexer.Block.FetcherTest do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> block_quantity = integer_to_quantity(block_number) from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" to_address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" @@ -614,7 +614,7 @@ defmodule Indexer.Block.FetcherTest do assert fifth_address.fetched_coin_balance == %Wei{value: Decimal.new(930_417_572_224_879_702_000)} assert fifth_address.fetched_coin_balance_block_number == block_number - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> assert {:ok, %{ inserted: %{ diff --git a/apps/indexer/test/indexer/fetcher/block_reward_test.exs b/apps/indexer/test/indexer/fetcher/block_reward_test.exs index 6e7b2377a223..2cf2c01819d3 100644 --- a/apps/indexer/test/indexer/fetcher/block_reward_test.exs +++ b/apps/indexer/test/indexer/fetcher/block_reward_test.exs @@ -30,7 +30,7 @@ defmodule Indexer.Fetcher.BlockRewardTest do transport: EthereumJSONRPC.Mox, transport_options: [], # Which one does not matter, so pick one - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ] } end diff --git a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance_test.exs index 12acf46a0913..708a1e058663 100644 --- a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs +++ b/apps/indexer/test/indexer/fetcher/coin_balance_test.exs @@ -39,7 +39,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do miner_hash_data: "0xe6a7a1d47ff21b6321162aea7c6cb457d5476bca" } - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> %{ block_number: 34, fetched_balance: 252_460_834_000_000_000_000_000_000, @@ -110,7 +110,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do miner_hash_data: "0xe6a7a1d47ff21b6321162aea7c6cb457d5476bca" } - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> %{ block_number: 34, fetched_balance: 252_460_834_000_000_000_000_000_000, @@ -184,7 +184,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do } } - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> %{ block_number: 34, fetched_balance: 252_460_834_000_000_000_000_000_000, @@ -254,7 +254,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do hash_data: "0x05a56e2d52c817161883f50c441c3228cfe54d9f" } - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> %{ balance_by_block_number: %{ 1 => 252_460_801_000_000_000_000_000_000, @@ -360,7 +360,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do transport: EthereumJSONRPC.Mox, transport_options: [], # Which one does not matter, so pick one - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ] } end diff --git a/apps/indexer/test/indexer/fetcher/contract_code_test.exs b/apps/indexer/test/indexer/fetcher/contract_code_test.exs index 210bafc81863..a269ee997b6e 100644 --- a/apps/indexer/test/indexer/fetcher/contract_code_test.exs +++ b/apps/indexer/test/indexer/fetcher/contract_code_test.exs @@ -24,8 +24,8 @@ defmodule Indexer.Fetcher.ContractCodeTest do end describe "async_fetch/1" do - # geth test node on circle ci is synced farther than parity - @tag :no_parity + # geth test node on circle ci is synced farther than Nethermind + @tag :no_nethermind test "fetched codes for address_hashes", %{json_rpc_named_arguments: json_rpc_named_arguments} do variant = Keyword.fetch!(json_rpc_named_arguments, :variant) @@ -47,7 +47,7 @@ defmodule Indexer.Fetcher.ContractCodeTest do } } - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> %{ block_number: 348_179, code: diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index 1c56536117e8..1ea2122aa009 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -2,6 +2,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do use EthereumJSONRPC.Case, async: false use Explorer.DataCase + import ExUnit.CaptureLog import Mox alias Explorer.Chain @@ -21,7 +22,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do } do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> EthereumJSONRPC.Mox |> expect(:json_rpc, fn _json, _options -> {:ok, @@ -81,7 +82,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do } do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> EthereumJSONRPC.Mox |> expect(:json_rpc, fn [%{id: id}], _options -> {:ok, @@ -100,7 +101,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do block_number = 1_000_006 block = insert(:block, number: block_number) - insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block.hash) assert :ok = InternalTransaction.run([block_number], json_rpc_named_arguments) @@ -116,7 +117,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do json_rpc_named_arguments: json_rpc_named_arguments } do block = insert(:block) - insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block.hash) assert InternalTransaction.init( [], @@ -130,7 +131,6 @@ defmodule Indexer.Fetcher.InternalTransactionTest do json_rpc_named_arguments: json_rpc_named_arguments } do block = insert(:block) - insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: false) assert InternalTransaction.init( [], @@ -146,7 +146,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do } do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> EthereumJSONRPC.Mox |> expect(:json_rpc, fn [%{id: id}], _options -> {:ok, @@ -169,7 +169,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do block = insert(:block) block_hash = block.hash - insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block_hash) assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash) @@ -184,11 +184,11 @@ defmodule Indexer.Fetcher.InternalTransactionTest do block = insert(:block) transaction = insert(:transaction) |> with_block(block) block_hash = block.hash - insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block_hash) if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Parity -> + EthereumJSONRPC.Nethermind -> EthereumJSONRPC.Mox |> expect(:json_rpc, fn [%{id: id, method: "trace_replayBlockTransactions"}], _options -> {:ok, @@ -297,7 +297,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do block = insert(:block) insert(:transaction) |> with_block(block) block_hash = block.hash - insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block_hash) assert %{block_hash: ^block_hash} = Repo.get(PendingBlockOperation, block_hash) @@ -305,5 +305,165 @@ defmodule Indexer.Fetcher.InternalTransactionTest do assert %{block_hash: ^block_hash} = Repo.get(PendingBlockOperation, block_hash) end + + test "remove block consensus on foreign_key_violation", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + block = insert(:block) + transaction = :transaction |> insert() |> with_block(block) + block_number = block.number + insert(:pending_block_operation, block_hash: block.hash) + + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + case Keyword.fetch!(json_rpc_named_arguments, :variant) do + EthereumJSONRPC.Nethermind -> + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn [%{id: id, method: "trace_replayBlockTransactions"}], _options -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "output" => "0x", + "stateDiff" => nil, + "trace" => [ + %{ + "action" => %{ + "callType" => "call", + "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", + "gas" => "0x8600", + "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", + "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", + "value" => "0x174876e800" + }, + "result" => %{"gasUsed" => "0x7d37", "output" => "0x"}, + "subtraces" => 1, + "traceAddress" => [], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0xb37b428a7ddee91f39b26d79d23dc1c89e3e12a7", + "gas" => "0x32dcf", + "input" => "0x42dad49e", + "to" => "0xee4019030fb5c2b68c42105552c6268d56c6cbfe", + "value" => "0x0" + }, + "result" => %{ + "gasUsed" => "0xb08", + "output" => "0x" + }, + "subtraces" => 0, + "traceAddress" => [0], + "type" => "call" + } + ], + "transactionHash" => transaction.hash, + "vmTrace" => nil + }, + %{ + "output" => "0x", + "stateDiff" => nil, + "trace" => [ + %{ + "action" => %{ + "callType" => "call", + "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", + "gas" => "0x8600", + "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", + "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", + "value" => "0x174876e800" + }, + "result" => %{"gasUsed" => "0x7d37", "output" => "0x"}, + "subtraces" => 1, + "traceAddress" => [], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0xb37b428a7ddee91f39b26d79d23dc1c89e3e12a7", + "gas" => "0x32dcf", + "input" => "0x42dad49e", + "to" => "0xee4019030fb5c2b68c42105552c6268d56c6cbfe", + "value" => "0x0" + }, + "result" => %{ + "gasUsed" => "0xb08", + "output" => "0x" + }, + "subtraces" => 0, + "traceAddress" => [0], + "type" => "call" + } + ], + "transactionHash" => transaction_hash(), + "vmTrace" => nil + } + ] + } + ]} + end) + + EthereumJSONRPC.Geth -> + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn [%{id: id, method: "debug_traceTransaction"}], _options -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "blockNumber" => block.number, + "transactionIndex" => 0, + "transactionHash" => transaction.hash, + "index" => 0, + "traceAddress" => [], + "type" => "call", + "callType" => "call", + "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", + "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", + "gas" => "0x8600", + "gasUsed" => "0x7d37", + "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", + "output" => "0x", + "value" => "0x174876e800" + }, + %{ + "blockNumber" => block.number, + "transactionIndex" => 0, + "transactionHash" => transaction_hash(), + "index" => 0, + "traceAddress" => [], + "type" => "call", + "callType" => "call", + "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", + "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", + "gas" => "0x8600", + "gasUsed" => "0x7d37", + "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", + "output" => "0x", + "value" => "0x174876e800" + } + ] + } + ]} + end) + + variant_name -> + raise ArgumentError, "Unsupported variant name (#{variant_name})" + end + end + + logs = + capture_log(fn -> + assert {:retry, [^block_number]} = InternalTransaction.run([block_number], json_rpc_named_arguments) + end) + + assert %{consensus: false} = Repo.reload(block) + assert logs =~ "foreign_key_violation on internal transactions import, foreign transactions hashes:" + end end end diff --git a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs index e8a656287600..052b4b75f492 100644 --- a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs +++ b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs @@ -29,7 +29,7 @@ defmodule Indexer.Fetcher.UncleBlockTest do transport: EthereumJSONRPC.Mox, transport_options: [], # Which one does not matter, so pick one - variant: EthereumJSONRPC.Parity + variant: EthereumJSONRPC.Nethermind ] } end diff --git a/apps/indexer/test/indexer/pending_ops_cleaner_test.exs b/apps/indexer/test/indexer/pending_ops_cleaner_test.exs index f8f1cbdb30b6..ceb5c530f28e 100644 --- a/apps/indexer/test/indexer/pending_ops_cleaner_test.exs +++ b/apps/indexer/test/indexer/pending_ops_cleaner_test.exs @@ -8,7 +8,7 @@ defmodule Indexer.PendingOpsCleanerTest do test "deletes non-consensus pending ops on init" do block = insert(:block, consensus: false) - insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block.hash) assert Repo.one(from(block in PendingBlockOperation, where: block.block_hash == ^block.hash)) @@ -24,7 +24,7 @@ defmodule Indexer.PendingOpsCleanerTest do block = insert(:block, consensus: false) - insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + insert(:pending_block_operation, block_hash: block.hash) Process.sleep(2_000) diff --git a/apps/indexer/test/indexer/transform/address_token_balances_test.exs b/apps/indexer/test/indexer/transform/address_token_balances_test.exs index 82009ffab2f4..04111ff26215 100644 --- a/apps/indexer/test/indexer/transform/address_token_balances_test.exs +++ b/apps/indexer/test/indexer/transform/address_token_balances_test.exs @@ -25,7 +25,7 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do from_address_hash: from_address_hash, to_address_hash: to_address_hash, token_contract_address_hash: token_contract_address_hash, - token_id: nil, + token_ids: nil, token_type: "ERC-20" } @@ -49,7 +49,7 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do to_address_hash: to_address_hash, token_contract_address_hash: token_contract_address_hash, token_type: "ERC-721", - token_id: nil + token_ids: nil } params_set = AddressTokenBalances.params_set(%{token_transfers_params: [token_transfer_params]}) diff --git a/apps/indexer/test/indexer/transform/token_transfers_test.exs b/apps/indexer/test/indexer/transform/token_transfers_test.exs index dbea962c321e..c21947bf8977 100644 --- a/apps/indexer/test/indexer/transform/token_transfers_test.exs +++ b/apps/indexer/test/indexer/transform/token_transfers_test.exs @@ -68,13 +68,13 @@ defmodule Indexer.Transform.TokenTransfersTest do from_address_hash: truncated_hash(log_3.second_topic), to_address_hash: truncated_hash(log_3.third_topic), token_contract_address_hash: log_3.address_hash, - token_id: 183, + token_ids: [183], transaction_hash: log_3.transaction_hash, token_type: "ERC-721", block_hash: log_3.block_hash }, %{ - token_id: nil, + token_ids: nil, amount: Decimal.new(17_000_000_000_000_000_000), block_number: log_1.block_number, log_index: log_1.index, @@ -122,7 +122,7 @@ defmodule Indexer.Transform.TokenTransfersTest do to_address_hash: "0xbe8cdfc13ffda20c844ac3da2b53a23ac5787f1e", block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", token_contract_address_hash: log.address_hash, - token_id: 14_939, + token_ids: [14_939], transaction_hash: log.transaction_hash, token_type: "ERC-721" } @@ -158,8 +158,9 @@ defmodule Indexer.Transform.TokenTransfersTest do log_index: 2, to_address_hash: "0x9c978f4cfa1fe13406bcc05baf26a35716f881dd", token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb", - token_id: - 7_237_005_577_332_282_011_952_059_972_634_123_378_909_214_838_582_411_639_295_170_840_059_424_276_480, + token_ids: [ + 7_237_005_577_332_282_011_952_059_972_634_123_378_909_214_838_582_411_639_295_170_840_059_424_276_480 + ], token_type: "ERC-1155", transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8" } @@ -198,7 +199,6 @@ defmodule Indexer.Transform.TokenTransfersTest do log_index: 2, to_address_hash: "0x6c943470780461b00783ad530a53913bd2c104d3", token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb", - token_id: nil, token_ids: [680_564_733_841_876_926_926_749_214_863_536_422_912], token_type: "ERC-1155", transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", diff --git a/bin/install_chrome_headless.sh b/bin/install_chrome_headless.sh index c27512c776d6..1b365ed7a861 100755 --- a/bin/install_chrome_headless.sh +++ b/bin/install_chrome_headless.sh @@ -1,7 +1,7 @@ export DISPLAY=:99.0 sh -e /etc/init.d/xvfb start # export CHROMEDRIVER_VERSION=`curl -s http://chromedriver.storage.googleapis.com/LATEST_RELEASE` -export CHROMEDRIVER_VERSION=`92.0.4515.43` +export CHROMEDRIVER_VERSION=`104.0.5112.79` curl -L -O "http://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" unzip chromedriver_linux64.zip sudo chmod +x chromedriver diff --git a/config/config.exs b/config/config.exs index ffdbc800322b..8ec1d3431278 100644 --- a/config/config.exs +++ b/config/config.exs @@ -35,7 +35,8 @@ config :logger, {LoggerFileBackend, :pending_transactions_to_refetch}, {LoggerFileBackend, :empty_blocks_to_refetch}, {LoggerFileBackend, :api}, - {LoggerFileBackend, :block_import_timings} + {LoggerFileBackend, :block_import_timings}, + {LoggerFileBackend, :account} ] config :logger, :console, diff --git a/config/dev.exs b/config/dev.exs index 21e6c9fd40f7..323212e107f7 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -8,3 +8,8 @@ config :logger, :ecto, path: Path.absname("logs/dev/ecto.log") config :logger, :error, path: Path.absname("logs/dev/error.log") + +config :logger, :account, + level: :debug, + path: Path.absname("logs/dev/account.log"), + metadata_filter: [fetcher: :account] diff --git a/config/prod.exs b/config/prod.exs index 2175810d041f..1d9be54e3fc6 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -12,3 +12,9 @@ config :logger, :ecto, config :logger, :error, path: Path.absname("logs/prod/error.log"), rotate: %{max_bytes: 52_428_800, keep: 19} + +config :logger, :account, + level: :info, + path: Path.absname("logs/prod/account.log"), + rotate: %{max_bytes: 52_428_800, keep: 19}, + metadata_filter: [fetcher: :account] diff --git a/config/runtime.exs b/config/runtime.exs index 02d0097b79b0..8ed002d3e4bc 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -2,26 +2,510 @@ import Config import Bitwise +indexer_memory_limit_default = 1 + indexer_memory_limit = "INDEXER_MEMORY_LIMIT" - |> System.get_env("1") + |> System.get_env(to_string(indexer_memory_limit_default)) |> Integer.parse() |> case do {integer, ""} -> integer - _ -> 1 + _ -> indexer_memory_limit_default end config :indexer, memory_limit: indexer_memory_limit <<< 30 +indexer_empty_blocks_sanitizer_batch_size_default = 100 + indexer_empty_blocks_sanitizer_batch_size = - if System.get_env("INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE") do - case Integer.parse(System.get_env("INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE")) do - {integer, ""} -> integer - _ -> 100 - end - else - 100 + "INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE" + |> System.get_env(to_string(indexer_empty_blocks_sanitizer_batch_size_default)) + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> indexer_empty_blocks_sanitizer_batch_size_default end config :indexer, Indexer.Fetcher.EmptyBlocksSanitizer, batch_size: indexer_empty_blocks_sanitizer_batch_size + +config :block_scout_web, :footer, + chat_link: System.get_env("FOOTER_CHAT_LINK", "https://discord.gg/blockscout"), + forum_link: System.get_env("FOOTER_FORUM_LINK", "https://forum.poa.network/c/blockscout"), + github_link: System.get_env("FOOTER_GITHUB_LINK", "https://github.com/blockscout/blockscout"), + enable_forum_link: System.get_env("FOOTER_ENABLE_FORUM_LINK", "false") == "true" + +###################### +### BlockScout Web ### +###################### + +# Configures Ueberauth's Auth0 auth provider +config :ueberauth, Ueberauth.Strategy.Auth0.OAuth, + domain: System.get_env("ACCOUNT_AUTH0_DOMAIN"), + client_id: System.get_env("ACCOUNT_AUTH0_CLIENT_ID"), + client_secret: System.get_env("ACCOUNT_AUTH0_CLIENT_SECRET") + +# Configures Ueberauth local settings +config :ueberauth, Ueberauth, + logout_url: System.get_env("ACCOUNT_AUTH0_LOGOUT_URL"), + logout_return_to_url: System.get_env("ACCOUNT_AUTH0_LOGOUT_RETURN_URL") + +config :block_scout_web, + version: System.get_env("BLOCKSCOUT_VERSION"), + release_link: System.get_env("RELEASE_LINK"), + decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN"), + show_percentage: if(System.get_env("SHOW_ADDRESS_MARKETCAP_PERCENTAGE", "true") == "false", do: false, else: true), + checksum_address_hashes: if(System.get_env("CHECKSUM_ADDRESS_HASHES", "true") == "false", do: false, else: true) + +config :block_scout_web, BlockScoutWeb.Chain, + network: System.get_env("NETWORK"), + subnetwork: System.get_env("SUBNETWORK"), + network_icon: System.get_env("NETWORK_ICON"), + logo: System.get_env("LOGO"), + logo_footer: System.get_env("LOGO_FOOTER"), + logo_text: System.get_env("LOGO_TEXT"), + has_emission_funds: false, + show_maintenance_alert: System.get_env("SHOW_MAINTENANCE_ALERT", "false") == "true", + enable_testnet_label: System.get_env("SHOW_TESTNET_LABEL", "false") == "true", + testnet_label_text: System.get_env("TESTNET_LABEL_TEXT", "Testnet") + +verification_max_libraries_default = 10 + +verification_max_libraries = + "CONTRACT_VERIFICATION_MAX_LIBRARIES" + |> System.get_env(to_string(verification_max_libraries_default)) + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> verification_max_libraries_default + end + +config :block_scout_web, + link_to_other_explorers: System.get_env("LINK_TO_OTHER_EXPLORERS") == "true", + other_explorers: System.get_env("OTHER_EXPLORERS"), + other_networks: System.get_env("SUPPORTED_CHAINS"), + webapp_url: System.get_env("WEBAPP_URL"), + api_url: System.get_env("API_URL"), + apps_menu: if(System.get_env("APPS_MENU", "false") == "true", do: true, else: false), + apps: System.get_env("APPS") || System.get_env("EXTERNAL_APPS"), + gas_price: System.get_env("GAS_PRICE", nil), + dark_forest_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST"), + 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_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), + new_tags: System.get_env("NEW_TAGS"), + chain_id: System.get_env("CHAIN_ID"), + json_rpc: System.get_env("JSON_RPC"), + verification_max_libraries: verification_max_libraries + +default_api_rate_limit = 50 +default_api_rate_limit_str = Integer.to_string(default_api_rate_limit) + +global_api_rate_limit_value = + "API_RATE_LIMIT" + |> System.get_env(default_api_rate_limit_str) + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> default_api_rate_limit + end + +api_rate_limit_by_key_value = + "API_RATE_LIMIT_BY_KEY" + |> System.get_env(default_api_rate_limit_str) + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> default_api_rate_limit + end + +api_rate_limit_by_ip_value = + "API_RATE_LIMIT_BY_IP" + |> System.get_env(default_api_rate_limit_str) + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> default_api_rate_limit + end + +config :block_scout_web, :api_rate_limit, + global_limit: global_api_rate_limit_value, + limit_by_key: api_rate_limit_by_key_value, + limit_by_ip: api_rate_limit_by_ip_value, + static_api_key: System.get_env("API_RATE_LIMIT_STATIC_API_KEY", nil), + whitelisted_ips: System.get_env("API_RATE_LIMIT_WHITELISTED_IPS", nil) + +config :block_scout_web, BlockScoutWeb.Endpoint, + server: true, + url: [ + scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "http", + host: System.get_env("BLOCKSCOUT_HOST") || "localhost" + ] + +# Configures History +price_chart_config = + if System.get_env("SHOW_PRICE_CHART", "false") != "false" do + %{market: [:price, :market_cap]} + else + %{} + end + +tx_chart_config = + if System.get_env("SHOW_TXS_CHART", "true") == "true" do + %{transactions: [:transactions_per_day]} + else + %{} + end + +config :block_scout_web, + chart_config: Map.merge(price_chart_config, tx_chart_config) + +config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, + # days + coin_balance_history_days: System.get_env("COIN_BALANCE_HISTORY_DAYS", "10") + +config :block_scout_web, BlockScoutWeb.API.V2, enabled: System.get_env("API_V2_ENABLED") == "true" + +######################## +### Ethereum JSONRPC ### +######################## + +config :ethereum_jsonrpc, + rpc_transport: if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", do: :http, else: :ipc), + ipc_path: System.get_env("IPC_PATH"), + disable_archive_balances?: System.get_env("ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES", "false") == "true" + +debug_trace_transaction_timeout = System.get_env("ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT", "5s") +config :ethereum_jsonrpc, EthereumJSONRPC.Geth, debug_trace_transaction_timeout: debug_trace_transaction_timeout + +config :ethereum_jsonrpc, EthereumJSONRPC.PendingTransaction, + type: System.get_env("ETHEREUM_JSONRPC_PENDING_TRANSACTIONS_TYPE", "default") + +################ +### Explorer ### +################ + +disable_indexer = System.get_env("DISABLE_INDEXER") +disable_webapp = System.get_env("DISABLE_WEBAPP") + +healthy_blocks_period = + System.get_env("HEALTHY_BLOCKS_PERIOD", "5") + |> Integer.parse() + |> elem(0) + |> :timer.minutes() + +config :explorer, + coin: System.get_env("COIN", nil) || System.get_env("EXCHANGE_RATES_COIN") || "ETH", + coin_name: System.get_env("COIN_NAME", nil) || System.get_env("EXCHANGE_RATES_COIN") || "ETH", + allowed_evm_versions: + System.get_env("ALLOWED_EVM_VERSIONS") || + "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,default", + include_uncles_in_average_block_time: + if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "true", do: true, else: false), + healthy_blocks_period: healthy_blocks_period, + realtime_events_sender: + if(disable_webapp != "true", + do: Explorer.Chain.Events.SimpleSender, + else: Explorer.Chain.Events.DBSender + ), + enable_caching_implementation_data_of_proxy: true, + avg_block_time_as_ttl_cached_implementation_data_of_proxy: true, + fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(4), + implementation_data_fetching_timeout: :timer.seconds(2), + restricted_list: System.get_env("RESTRICTED_LIST", nil), + restricted_list_key: System.get_env("RESTRICTED_LIST_KEY", nil) + +config :explorer, Explorer.Visualize.Sol2uml, + service_url: System.get_env("VISUALIZE_SOL2UML_SERVICE_URL"), + enabled: System.get_env("VISUALIZE_SOL2UML_ENABLED") == "true" + +config :explorer, Explorer.Chain.Events.Listener, + enabled: + if(disable_webapp == "true" && disable_indexer == "true", + do: false, + else: true + ) + +config :explorer, Explorer.ChainSpec.GenesisData, + chain_spec_path: System.get_env("CHAIN_SPEC_PATH"), + emission_format: System.get_env("EMISSION_FORMAT", "DEFAULT"), + rewards_contract_address: System.get_env("REWARDS_CONTRACT", "0xeca443e8e1ab29971a45a9c57a6a9875701698a5") + +config :explorer, Explorer.Chain.Cache.BlockNumber, + ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), + global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) + +address_sum_global_ttl = + "CACHE_ADDRESS_SUM_PERIOD" + |> System.get_env("") + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> 3600 + end + |> :timer.seconds() + +config :explorer, Explorer.Chain.Cache.AddressSum, global_ttl: address_sum_global_ttl + +config :explorer, Explorer.Chain.Cache.AddressSumMinusBurnt, global_ttl: address_sum_global_ttl + +config :explorer, Explorer.ExchangeRates, + store: :ets, + enabled: System.get_env("DISABLE_EXCHANGE_RATES") != "true", + coingecko_coin_id: System.get_env("EXCHANGE_RATES_COINGECKO_COIN_ID"), + coingecko_api_key: System.get_env("EXCHANGE_RATES_COINGECKO_API_KEY"), + coinmarketcap_api_key: System.get_env("EXCHANGE_RATES_COINMARKETCAP_API_KEY"), + fetch_btc_value: System.get_env("EXCHANGE_RATES_FETCH_BTC_VALUE") == "true" + +exchange_rates_source = + cond do + System.get_env("EXCHANGE_RATES_SOURCE") == "coin_gecko" -> Explorer.ExchangeRates.Source.CoinGecko + System.get_env("EXCHANGE_RATES_SOURCE") == "coin_market_cap" -> Explorer.ExchangeRates.Source.CoinMarketCap + true -> Explorer.ExchangeRates.Source.CoinGecko + end + +config :explorer, Explorer.ExchangeRates.Source, source: exchange_rates_source + +config :explorer, Explorer.KnownTokens, enabled: System.get_env("DISABLE_KNOWN_TOKENS") != "true", store: :ets + +config :explorer, Explorer.Market.History.Cataloger, enabled: disable_indexer != "true" + +txs_stats_init_lag = + System.get_env("TXS_HISTORIAN_INIT_LAG", "0") + |> Integer.parse() + |> elem(0) + |> :timer.minutes() + +txs_stats_days_to_compile_at_init = + System.get_env("TXS_STATS_DAYS_TO_COMPILE_AT_INIT", "40") + |> Integer.parse() + |> elem(0) + +config :explorer, Explorer.Chain.Transaction.History.Historian, + enabled: System.get_env("ENABLE_TXS_STATS", "true") != "false", + init_lag: txs_stats_init_lag, + days_to_compile_at_init: txs_stats_days_to_compile_at_init + +history_fetch_interval = + case Integer.parse(System.get_env("HISTORY_FETCH_INTERVAL", "")) do + {mins, ""} -> mins + _ -> 60 + end + |> :timer.minutes() + +config :explorer, Explorer.History.Process, history_fetch_interval: history_fetch_interval + +if System.get_env("METADATA_CONTRACT") && System.get_env("VALIDATORS_CONTRACT") do + config :explorer, Explorer.Validator.MetadataRetriever, + metadata_contract_address: System.get_env("METADATA_CONTRACT"), + validators_contract_address: System.get_env("VALIDATORS_CONTRACT") + + config :explorer, Explorer.Validator.MetadataProcessor, enabled: disable_indexer != "true" +else + config :explorer, Explorer.Validator.MetadataProcessor, enabled: false +end + +config :explorer, Explorer.Chain.Block.Reward, + validators_contract_address: System.get_env("VALIDATORS_CONTRACT"), + keys_manager_contract_address: System.get_env("KEYS_MANAGER_CONTRACT") + +case System.get_env("SUPPLY_MODULE") do + "rsk" -> + config :explorer, supply: Explorer.Chain.Supply.RSK + + _ -> + :ok +end + +config :explorer, + checksum_function: System.get_env("CHECKSUM_FUNCTION") && String.to_atom(System.get_env("CHECKSUM_FUNCTION")) + +config :explorer, Explorer.Chain.Cache.Blocks, + ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), + global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.Chain.Cache.Transactions, + ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), + global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.Chain.Cache.TransactionsApiV2, + ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), + global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.Chain.Cache.Accounts, + ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), + global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.Chain.Cache.Uncles, + ttl_check_interval: if(disable_indexer == "true", do: :timer.seconds(1), else: false), + global_ttl: if(disable_indexer == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.ThirdPartyIntegrations.Sourcify, + server_url: System.get_env("SOURCIFY_SERVER_URL") || "https://sourcify.dev/server", + enabled: System.get_env("ENABLE_SOURCIFY_INTEGRATION") == "true", + chain_id: System.get_env("CHAIN_ID"), + repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" + +config :explorer, Explorer.SmartContract.RustVerifierInterface, + service_url: System.get_env("RUST_VERIFICATION_SERVICE_URL"), + enabled: System.get_env("ENABLE_RUST_VERIFICATION_SERVICE") == "true" + +config :explorer, Explorer.ThirdPartyIntegrations.AirTable, + table_url: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL"), + api_key: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY") + +config :explorer, Explorer.Mailer, + adapter: Bamboo.SendGridAdapter, + api_key: System.get_env("ACCOUNT_SENDGRID_API_KEY") + +config :explorer, Explorer.Account, + enabled: System.get_env("ACCOUNT_ENABLED") == "true", + sendgrid: [ + sender: System.get_env("ACCOUNT_SENDGRID_SENDER"), + template: System.get_env("ACCOUNT_SENDGRID_TEMPLATE") + ] + +{token_id_migration_first_block, _} = Integer.parse(System.get_env("TOKEN_ID_MIGRATION_FIRST_BLOCK", "0")) +{token_id_migration_concurrency, _} = Integer.parse(System.get_env("TOKEN_ID_MIGRATION_CONCURRENCY", "1")) +{token_id_migration_batch_size, _} = Integer.parse(System.get_env("TOKEN_ID_MIGRATION_BATCH_SIZE", "500")) + +config :explorer, :token_id_migration, + first_block: token_id_migration_first_block, + concurrency: token_id_migration_concurrency, + batch_size: token_id_migration_batch_size + +############### +### Indexer ### +############### + +block_transformers = %{ + "clique" => Indexer.Transform.Blocks.Clique, + "base" => Indexer.Transform.Blocks.Base +} + +# Compile time environment variable access requires recompilation. +configured_transformer = System.get_env("BLOCK_TRANSFORMER") || "base" + +block_transformer = + case Map.get(block_transformers, configured_transformer) do + nil -> + raise """ + No such block transformer: #{configured_transformer}. + + Valid values are: + #{Enum.join(Map.keys(block_transformers), "\n")} + + Please update environment variable BLOCK_TRANSFORMER accordingly. + """ + + transformer -> + transformer + end + +config :indexer, + block_transformer: block_transformer, + metadata_updater_seconds_interval: + String.to_integer(System.get_env("TOKEN_METADATA_UPDATE_INTERVAL") || "#{2 * 24 * 60 * 60}"), + block_ranges: System.get_env("BLOCK_RANGES") || "", + first_block: System.get_env("FIRST_BLOCK") || "", + last_block: System.get_env("LAST_BLOCK") || "", + trace_first_block: System.get_env("TRACE_FIRST_BLOCK") || "", + trace_last_block: System.get_env("TRACE_LAST_BLOCK") || "", + fetch_rewards_way: System.get_env("FETCH_REWARDS_WAY", "trace_block") + +{receipts_batch_size, _} = Integer.parse(System.get_env("INDEXER_RECEIPTS_BATCH_SIZE", "250")) +{receipts_concurrency, _} = Integer.parse(System.get_env("INDEXER_RECEIPTS_CONCURRENCY", "10")) + +config :indexer, + receipts_batch_size: receipts_batch_size, + receipts_concurrency: receipts_concurrency + +config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor, + disabled?: + System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu" || + System.get_env("INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER", "false") == "true" + +token_balance_on_demand_fetcher_threshold_minutes = System.get_env("TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES") + +token_balance_on_demand_fetcher_threshold = + case token_balance_on_demand_fetcher_threshold_minutes && + Integer.parse(token_balance_on_demand_fetcher_threshold_minutes) do + {integer, ""} -> integer + _ -> 60 + end + +config :indexer, Indexer.Fetcher.TokenBalanceOnDemand, threshold: token_balance_on_demand_fetcher_threshold + +coin_balance_on_demand_fetcher_threshold_minutes = System.get_env("COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES") + +coin_balance_on_demand_fetcher_threshold = + case coin_balance_on_demand_fetcher_threshold_minutes && + Integer.parse(coin_balance_on_demand_fetcher_threshold_minutes) do + {integer, ""} -> integer + _ -> 60 + end + +config :indexer, Indexer.Fetcher.CoinBalanceOnDemand, threshold: coin_balance_on_demand_fetcher_threshold + +config :indexer, Indexer.Fetcher.BlockReward.Supervisor, + disabled?: System.get_env("INDEXER_DISABLE_BLOCK_REWARD_FETCHER", "false") == "true" + +config :indexer, Indexer.Fetcher.InternalTransaction.Supervisor, + disabled?: System.get_env("INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER", "false") == "true" + +config :indexer, Indexer.Fetcher.CoinBalance.Supervisor, + disabled?: System.get_env("INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER", "false") == "true" + +config :indexer, Indexer.Fetcher.TokenUpdater.Supervisor, + disabled?: System.get_env("INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER", "false") == "true" + +config :indexer, Indexer.Fetcher.EmptyBlocksSanitizer.Supervisor, + disabled?: System.get_env("INDEXER_DISABLE_EMPTY_BLOCK_SANITIZER", "false") == "true" + +config :indexer, Indexer.Supervisor, enabled: System.get_env("DISABLE_INDEXER") != "true" + +config :indexer, Indexer.Block.Realtime.Supervisor, enabled: System.get_env("DISABLE_REALTIME_INDEXER") != "true" + +config :indexer, Indexer.Fetcher.TokenInstance.Supervisor, + disabled?: System.get_env("DISABLE_TOKEN_INSTANCE_FETCHER", "false") == "true" + +blocks_catchup_fetcher_batch_size_default_str = "10" +blocks_catchup_fetcher_concurrency_default_str = "10" + +{blocks_catchup_fetcher_batch_size, _} = + Integer.parse(System.get_env("INDEXER_CATCHUP_BLOCKS_BATCH_SIZE", blocks_catchup_fetcher_batch_size_default_str)) + +{blocks_catchup_fetcher_concurrency, _} = + Integer.parse(System.get_env("INDEXER_CATCHUP_BLOCKS_CONCURRENCY", blocks_catchup_fetcher_concurrency_default_str)) + +config :indexer, Indexer.Block.Catchup.Fetcher, + batch_size: blocks_catchup_fetcher_batch_size, + concurrency: blocks_catchup_fetcher_concurrency + +{internal_transaction_fetcher_batch_size, _} = + Integer.parse(System.get_env("INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE", "10")) + +{internal_transaction_fetcher_concurrency, _} = + Integer.parse(System.get_env("INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY", "4")) + +config :indexer, Indexer.Fetcher.InternalTransaction, + batch_size: internal_transaction_fetcher_batch_size, + concurrency: internal_transaction_fetcher_concurrency + +{coin_balance_fetcher_batch_size, _} = Integer.parse(System.get_env("INDEXER_COIN_BALANCES_BATCH_SIZE", "500")) + +{coin_balance_fetcher_concurrency, _} = Integer.parse(System.get_env("INDEXER_COIN_BALANCES_CONCURRENCY", "4")) + +config :indexer, Indexer.Fetcher.CoinBalance, + batch_size: coin_balance_fetcher_batch_size, + concurrency: coin_balance_fetcher_concurrency + +Code.require_file("#{config_env()}.exs", "config/runtime") + +for config <- "../apps/*/config/runtime/#{config_env()}.exs" |> Path.expand(__DIR__) |> Path.wildcard() do + Code.require_file("#{config_env()}.exs", Path.dirname(config)) +end diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs new file mode 100644 index 000000000000..17cea1504626 --- /dev/null +++ b/config/runtime/dev.exs @@ -0,0 +1,85 @@ +import Config + +alias EthereumJSONRPC.Variant +alias Explorer.Repo.ConfigHelper + +###################### +### BlockScout Web ### +###################### + +port = + case System.get_env("PORT") && Integer.parse(System.get_env("PORT")) do + {port, _} -> port + :error -> nil + nil -> nil + end + +config :block_scout_web, BlockScoutWeb.Endpoint, + secret_key_base: + System.get_env("SECRET_KEY_BASE") || "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5", + http: [ + port: port || 4000 + ], + url: [ + scheme: "http", + host: System.get_env("BLOCKSCOUT_HOST", "localhost") + ], + https: [ + port: (port && port + 1) || 4001, + cipher_suite: :strong, + certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem", + keyfile: System.get_env("KEYFILE") || "priv/cert/selfsigned_key.pem" + ] + +######################## +### Ethereum JSONRPC ### +######################## + +################ +### Explorer ### +################ + +database = if System.get_env("DATABASE_URL"), do: nil, else: "explorer_dev" +hostname = if System.get_env("DATABASE_URL"), do: nil, else: "localhost" + +pool_size = + if System.get_env("DATABASE_READ_ONLY_API_URL"), + do: ConfigHelper.get_db_pool_size("30"), + else: ConfigHelper.get_db_pool_size("40") + +# Configure your database +config :explorer, Explorer.Repo, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: pool_size + +database_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: database +hostname_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: hostname + +# Configure API database +config :explorer, Explorer.Repo.Replica1, + database: database_api, + hostname: hostname_api, + url: ConfigHelper.get_api_db_url(), + pool_size: ConfigHelper.get_api_db_pool_size("10") + +database_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: database +hostname_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: hostname + +# Configure Account database +config :explorer, Explorer.Repo.Account, + database: database_account, + hostname: hostname_account, + url: ConfigHelper.get_account_db_url(), + pool_size: ConfigHelper.get_account_db_pool_size("10") + +variant = Variant.get() + +Code.require_file("#{variant}.exs", "apps/explorer/config/dev") + +############### +### Indexer ### +############### + +Code.require_file("#{variant}.exs", "apps/indexer/config/dev") diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs new file mode 100644 index 000000000000..b5bdf68b66b0 --- /dev/null +++ b/config/runtime/prod.exs @@ -0,0 +1,64 @@ +import Config + +alias EthereumJSONRPC.Variant +alias Explorer.Repo.ConfigHelper + +###################### +### BlockScout Web ### +###################### + +config :block_scout_web, BlockScoutWeb.Endpoint, + secret_key_base: System.get_env("SECRET_KEY_BASE"), + check_origin: System.get_env("CHECK_ORIGIN", "false") == "true" || false, + http: [port: System.get_env("PORT")], + url: [ + scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "https", + port: System.get_env("PORT"), + host: System.get_env("BLOCKSCOUT_HOST") || "localhost" + ] + +######################## +### Ethereum JSONRPC ### +######################## + +################ +### Explorer ### +################ + +pool_size = + if System.get_env("DATABASE_READ_ONLY_API_URL"), + do: ConfigHelper.get_db_pool_size("50"), + else: ConfigHelper.get_db_pool_size("40") + +# Configures the database +config :explorer, Explorer.Repo, + url: System.get_env("DATABASE_URL"), + pool_size: pool_size, + ssl: ConfigHelper.ssl_enabled?() + +pool_size_api = + if System.get_env("DATABASE_READ_ONLY_API_URL"), + do: ConfigHelper.get_api_db_pool_size("50"), + else: ConfigHelper.get_api_db_pool_size("10") + +# Configures API the database +config :explorer, Explorer.Repo.Replica1, + url: ConfigHelper.get_api_db_url(), + pool_size: pool_size_api, + ssl: ConfigHelper.ssl_enabled?() + +# Configures Account database +config :explorer, Explorer.Repo.Account, + url: ConfigHelper.get_account_db_url(), + pool_size: ConfigHelper.get_account_db_pool_size("50"), + ssl: ConfigHelper.ssl_enabled?() + +variant = Variant.get() + +Code.require_file("#{variant}.exs", "apps/explorer/config/prod") + +############### +### Indexer ### +############### + +Code.require_file("#{variant}.exs", "apps/indexer/config/prod") diff --git a/config/runtime/test.exs b/config/runtime/test.exs new file mode 100644 index 000000000000..ca3eed98e10c --- /dev/null +++ b/config/runtime/test.exs @@ -0,0 +1,27 @@ +import Config + +alias EthereumJSONRPC.Variant + +###################### +### BlockScout Web ### +###################### + +config :block_scout_web, BlockScoutWeb.API.V2, enabled: true + +######################## +### Ethereum JSONRPC ### +######################## + +################ +### Explorer ### +################ + +variant = Variant.get() + +Code.require_file("#{variant}.exs", "apps/explorer/config/test") + +############### +### Indexer ### +############### + +Code.require_file("#{variant}.exs", "apps/indexer/config/test") diff --git a/docker-compose/README.md b/docker-compose/README.md index effe655f201b..39067753d663 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -1,31 +1,46 @@ # Docker-compose configuration -Runs BlockScout locally in Docker container with usage [docker-compose](https://github.com/docker/compose). +Runs Blockscout locally in Docker containers with [docker-compose](https://github.com/docker/compose). ## Prerequisites + - Docker v20.10+ - Docker-compose 2.x.x+ - Running Ethereum JSON RPC client ## Building Docker containers from source -``` + +```bash docker-compose up --build ``` -This command uses by-default `docker-compose.yml`, which build the explorer into Docker image and runs 2 Docker containers: -- one for the database. Postgres 13.x, which will be available at port 7432 on localhost -- and the BlockScout explorer at http://localhost:4000 +This command uses by-default `docker-compose.yml`, which builds the explorer into the Docker image and runs 3 Docker containers: + +- Postgres 14.x database, which will be available at port 7432 on localhost. +- [Smart-contract-verifier](https://github.com/blockscout/blockscout-rs/) service, which will be available at port 8043 on localhost. +- Blockscout explorer at http://localhost:4000. + +Note for Linux users: Linux users need to run the local node on http://0.0.0.0/ rather than http://127.0.0.1/ + +## Building Docker containers from source with native smart contract verification (deprecated) + +```bash +docker-compose -f docker-compose-no-rust-verification.yml up --build +``` ## Configs for different Ethereum clients -Also, the repo contains built-in configs for different clients without need to build the image + +The repo contains built-in configs for different clients without needing to build the image. + +- Erigon: `docker-compose -f docker-compose-no-build-erigon.yml up -d` +- Geth: `docker-compose -f docker-compose-no-build-geth.yml up -d` +- Nethermind, OpenEthereum: `docker-compose -f docker-compose-no-build-nethermind up -d` - Ganache: `docker-compose -f docker-compose-no-build-ganache.yml up -d` - HardHat network: `docker-compose -f docker-compose-no-build-hardhat-network.yml up -d` -- Geth: `docker-compose -f docker-compose-no-build-geth.yml up -d` -- OpenEthereum, Nethermind: `docker-compose -f docker-compose-no-build-open-ethereum-nethermind up -d` -- Running only explorer without DB: `docker-compose -f docker-compose-no-build-no-db-container.yml up -d`. In this case, one container is created - for the explorer itself. And it assumes that the DB credentials are provided through `DATABASE_URL` environment variable. +- Running explorer only without DB: `docker-compose -f docker-compose-no-build-no-db-container.yml up -d`. In this case, one container is created - for the explorer itself. It assumes DB credentials are provided through the `DATABASE_URL` environment variable. -All of the configs assume, that the Ethereum JSON RPC is running at http://localhost:8545. +All of the configs assume the Ethereum JSON RPC is running at http://localhost:8545. -In order to stop launched containers, run `docker-compose -d -f config_file.yml down`, where replace `config_file.yml` with the file name of the config, which has been launched before. +In order to stop launched containers, run `docker-compose -d -f config_file.yml down`, replacing `config_file.yml` with the file name of the config which was previously launched. -You can play with the BlockScout environment variables, which are present at `./envs/common-blockscout.env`. The description of the environment variables are available in [the docs](https://docs.blockscout.com/for-developers/information-and-settings/env-variables). \ No newline at end of file +You can adjust BlockScout environment variables from `./envs/common-blockscout.env`. Descriptions of the ENVs are available in [the docs](https://docs.blockscout.com/for-developers/information-and-settings/env-variables). diff --git a/docker-compose/docker-compose-no-build-erigon.yml b/docker-compose/docker-compose-no-build-erigon.yml new file mode 100644 index 000000000000..b1640627a815 --- /dev/null +++ b/docker-compose/docker-compose-no-build-erigon.yml @@ -0,0 +1,70 @@ +version: '3.8' + +services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + + db: + image: postgres:14 + restart: always + container_name: 'postgres' + environment: + POSTGRES_PASSWORD: '' + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + volumes: + - ./postgres-data:/var/lib/postgresql/data + ports: + - 7432:5432 + + blockscout: + depends_on: + - db + - smart-contract-verifier + - redis_db + image: blockscout/blockscout:${DOCKER_TAG:-latest} + restart: always + container_name: 'blockscout' + links: + - db:database + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" + extra_hosts: + - 'host.docker.internal:host-gateway' + env_file: + - ./envs/common-blockscout.env + environment: + ETHEREUM_JSONRPC_VARIANT: 'erigon' + ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/ + ETHEREUM_JSONRPC_TRACE_URL: http://host.docker.internal:8545/ + DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false + ECTO_USE_SSL: 'false' + SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN' + ports: + - 4000:4000 + volumes: + - ./logs/:/app/logs/ + + smart-contract-verifier: + image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest} + restart: always + container_name: 'smart-contract-verifier' + env_file: + - ./envs/common-smart-contract-verifier.env + ports: + - 8043:8043 + + visualizer: + image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest} + restart: always + container_name: 'visualizer' + env_file: + - ./envs/common-visualizer.env + ports: + - 8050:8050 + diff --git a/docker-compose/docker-compose-no-build-ganache.yml b/docker-compose/docker-compose-no-build-ganache.yml index b90b58a7dd0c..177d402e47bf 100644 --- a/docker-compose/docker-compose-no-build-ganache.yml +++ b/docker-compose/docker-compose-no-build-ganache.yml @@ -1,8 +1,17 @@ version: '3.8' services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + db: - image: postgres:13.6 + image: postgres:14 restart: always container_name: 'postgres' environment: @@ -15,12 +24,14 @@ services: blockscout: depends_on: - db + - smart-contract-verifier + - redis_db image: blockscout/blockscout:${DOCKER_TAG:-latest} restart: always container_name: 'blockscout' links: - db:database - command: 'mix do ecto.create, ecto.migrate, phx.server' + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" extra_hosts: - 'host.docker.internal:host-gateway' env_file: @@ -29,9 +40,32 @@ services: ETHEREUM_JSONRPC_VARIANT: 'ganache' ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/ ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/ + INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER: 'true' INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true' DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false ECTO_USE_SSL: 'false' + SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN' + CHAIN_ID: '1337' ports: - 4000:4000 + volumes: + - ./logs/:/app/logs/ + + smart-contract-verifier: + image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest} + restart: always + container_name: 'smart-contract-verifier' + env_file: + - ./envs/common-smart-contract-verifier.env + ports: + - 8043:8043 + + visualizer: + image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest} + restart: always + container_name: 'visualizer' + env_file: + - ./envs/common-visualizer.env + ports: + - 8050:8050 diff --git a/docker-compose/docker-compose-no-build-geth.yml b/docker-compose/docker-compose-no-build-geth.yml index b6a87e090000..116ee4afe79a 100644 --- a/docker-compose/docker-compose-no-build-geth.yml +++ b/docker-compose/docker-compose-no-build-geth.yml @@ -1,8 +1,17 @@ version: '3.8' services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + db: - image: postgres:13.6 + image: postgres:14 restart: always container_name: 'postgres' environment: @@ -17,12 +26,14 @@ services: blockscout: depends_on: - db + - smart-contract-verifier + - redis_db image: blockscout/blockscout:${DOCKER_TAG:-latest} restart: always container_name: 'blockscout' links: - db:database - command: 'mix do ecto.create, ecto.migrate, phx.server' + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" extra_hosts: - 'host.docker.internal:host-gateway' env_file: @@ -33,6 +44,27 @@ services: ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/ DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false ECTO_USE_SSL: 'false' + SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN' ports: - 4000:4000 + volumes: + - ./logs/:/app/logs/ + + smart-contract-verifier: + image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest} + restart: always + container_name: 'smart-contract-verifier' + env_file: + - ./envs/common-smart-contract-verifier.env + ports: + - 8043:8043 + + visualizer: + image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest} + restart: always + container_name: 'visualizer' + env_file: + - ./envs/common-visualizer.env + ports: + - 8050:8050 diff --git a/docker-compose/docker-compose-no-build-hardhat-network.yml b/docker-compose/docker-compose-no-build-hardhat-network.yml index 73b241edd0e9..3748696d8353 100644 --- a/docker-compose/docker-compose-no-build-hardhat-network.yml +++ b/docker-compose/docker-compose-no-build-hardhat-network.yml @@ -1,8 +1,17 @@ version: '3.8' services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + db: - image: postgres:13.6 + image: postgres:14 restart: always container_name: 'postgres' environment: @@ -15,12 +24,14 @@ services: blockscout: depends_on: - db + - smart-contract-verifier + - redis_db image: blockscout/blockscout:${DOCKER_TAG:-latest} restart: always container_name: 'blockscout' links: - db:database - command: 'mix do ecto.create, ecto.migrate, phx.server' + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" extra_hosts: - 'host.docker.internal:host-gateway' env_file: @@ -32,6 +43,27 @@ services: INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true' DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false ECTO_USE_SSL: 'false' + SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN' ports: - 4000:4000 + volumes: + - ./logs/:/app/logs/ + + smart-contract-verifier: + image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest} + restart: always + container_name: 'smart-contract-verifier' + env_file: + - ./envs/common-smart-contract-verifier.env + ports: + - 8043:8043 + + visualizer: + image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest} + restart: always + container_name: 'visualizer' + env_file: + - ./envs/common-visualizer.env + ports: + - 8050:8050 diff --git a/docker-compose/docker-compose-no-build-nethermind.yml b/docker-compose/docker-compose-no-build-nethermind.yml new file mode 100644 index 000000000000..300d63c8278d --- /dev/null +++ b/docker-compose/docker-compose-no-build-nethermind.yml @@ -0,0 +1,70 @@ +version: '3.8' + +services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + + db: + image: postgres:14 + restart: always + container_name: 'postgres' + environment: + POSTGRES_PASSWORD: '' + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + volumes: + - ./postgres-data:/var/lib/postgresql/data + ports: + - 7432:5432 + + blockscout: + depends_on: + - db + - smart-contract-verifier + - redis_db + image: blockscout/blockscout:${DOCKER_TAG:-latest} + restart: always + container_name: 'blockscout' + links: + - db:database + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" + extra_hosts: + - 'host.docker.internal:host-gateway' + env_file: + - ./envs/common-blockscout.env + environment: + ETHEREUM_JSONRPC_VARIANT: 'nethermind' + ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/ + ETHEREUM_JSONRPC_TRACE_URL: http://host.docker.internal:8545/ + DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false + ECTO_USE_SSL: 'false' + SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN' + ports: + - 4000:4000 + volumes: + - ./logs/:/app/logs/ + + smart-contract-verifier: + image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest} + restart: always + container_name: 'smart-contract-verifier' + env_file: + - ./envs/common-smart-contract-verifier.env + ports: + - 8043:8043 + + visualizer: + image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest} + restart: always + container_name: 'visualizer' + env_file: + - ./envs/common-visualizer.env + ports: + - 8050:8050 + diff --git a/docker-compose/docker-compose-no-build-no-db-container.yml b/docker-compose/docker-compose-no-build-no-db-container.yml index d1689f61ff62..c73468c81cd0 100644 --- a/docker-compose/docker-compose-no-build-no-db-container.yml +++ b/docker-compose/docker-compose-no-build-no-db-container.yml @@ -1,11 +1,23 @@ version: '3.8' services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + blockscout: + depends_on: + - smart-contract-verifier + - redis_db image: blockscout/blockscout:${DOCKER_TAG:-latest} restart: always container_name: 'blockscout' - command: 'mix do ecto.create, ecto.migrate, phx.server' + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" extra_hosts: - 'host.docker.internal:host-gateway' env_file: @@ -15,6 +27,27 @@ services: ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/ DATABASE_URL: postgresql://postgres:@host.docker.internal:5432/blockscout?ssl=false ECTO_USE_SSL: 'false' + SECRET_KEY_BASE: '56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN' ports: - 4000:4000 + volumes: + - ./logs/:/app/logs/ + + smart-contract-verifier: + image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest} + restart: always + container_name: 'smart-contract-verifier' + env_file: + - ./envs/common-smart-contract-verifier.env + ports: + - 8043:8043 + + visualizer: + image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest} + restart: always + container_name: 'visualizer' + env_file: + - ./envs/common-visualizer.env + ports: + - 8050:8050 diff --git a/docker-compose/docker-compose-no-build-open-ethereum-nethermind.yml b/docker-compose/docker-compose-no-build-open-ethereum-nethermind.yml deleted file mode 100644 index a4b68a73c0e8..000000000000 --- a/docker-compose/docker-compose-no-build-open-ethereum-nethermind.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: '3.8' - -services: - db: - image: postgres:13.6 - restart: always - container_name: 'postgres' - environment: - POSTGRES_PASSWORD: '' - POSTGRES_USER: 'postgres' - POSTGRES_HOST_AUTH_METHOD: 'trust' - volumes: - - ./postgres-data:/var/lib/postgresql/data - ports: - - 7432:5432 - - blockscout: - depends_on: - - db - image: blockscout/blockscout:${DOCKER_TAG:-latest} - restart: always - container_name: 'blockscout' - links: - - db:database - command: 'mix do ecto.create, ecto.migrate, phx.server' - extra_hosts: - - 'host.docker.internal:host-gateway' - env_file: - - ./envs/common-blockscout.env - environment: - ETHEREUM_JSONRPC_VARIANT: 'parity' - ETHEREUM_JSONRPC_HTTP_URL: http://host.docker.internal:8545/ - ETHEREUM_JSONRPC_TRACE_URL: http://host.docker.internal:8545/ - DATABASE_URL: postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false - ECTO_USE_SSL: 'false' - ports: - - 4000:4000 - diff --git a/docker-compose/docker-compose-no-rust-services.yml b/docker-compose/docker-compose-no-rust-services.yml new file mode 100644 index 000000000000..f0ede3f16325 --- /dev/null +++ b/docker-compose/docker-compose-no-rust-services.yml @@ -0,0 +1,59 @@ +version: '3.8' + +services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + + db: + image: postgres:14 + restart: always + container_name: 'postgres' + environment: + POSTGRES_PASSWORD: '' + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + ports: + - 7432:5432 + + blockscout: + depends_on: + - db + - redis_db + image: blockscout/blockscout:${DOCKER_TAG:-latest} + build: + context: .. + dockerfile: ./docker/Dockerfile + args: + CACHE_EXCHANGE_RATES_PERIOD: "" + DISABLE_READ_API: "false" + API_PATH: "/" + NETWORK_PATH: "/" + DISABLE_WEBAPP: "false" + DISABLE_WRITE_API: "false" + CACHE_ENABLE_TOTAL_GAS_USAGE_COUNTER: "" + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" + SOCKET_ROOT: "/" + WOBSERVER_ENABLED: "false" + ADMIN_PANEL_ENABLED: "" + restart: always + container_name: 'blockscout' + links: + - db:database + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" + extra_hosts: + - 'host.docker.internal:host-gateway' + env_file: + - ./envs/common-blockscout.env + environment: + ENABLE_RUST_VERIFICATION_SERVICE: 'false' + VISUALIZE_SOL2UML_ENABLED: 'false' + ports: + - 4000:4000 + volumes: + - ./logs/:/app/logs/ diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index f7da1c2d62e7..77c41435f2f0 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,10 +1,21 @@ version: '3.8' services: + redis_db: + image: 'redis:alpine' + restart: always + ports: + - 6379:6379 + container_name: redis_db + command: redis-server + volumes: + - ./redis-data:/data + db: - image: postgres:13.6 + image: postgres:14 restart: always container_name: 'postgres' + command: postgres -c 'max_connections=200' environment: POSTGRES_PASSWORD: '' POSTGRES_USER: 'postgres' @@ -15,21 +26,52 @@ services: blockscout: depends_on: - db + - smart-contract-verifier + - redis_db image: blockscout/blockscout:${DOCKER_TAG:-latest} build: context: .. dockerfile: ./docker/Dockerfile args: - COIN: "" + CACHE_EXCHANGE_RATES_PERIOD: "" + DISABLE_READ_API: "false" + API_PATH: "/" + NETWORK_PATH: "/" + DISABLE_WEBAPP: "false" + DISABLE_WRITE_API: "false" + CACHE_ENABLE_TOTAL_GAS_USAGE_COUNTER: "" + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" + SOCKET_ROOT: "/" + WOBSERVER_ENABLED: "false" + ADMIN_PANEL_ENABLED: "" restart: always container_name: 'blockscout' links: - db:database - command: 'mix do ecto.create, ecto.migrate, phx.server' + command: bash -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" extra_hosts: - 'host.docker.internal:host-gateway' env_file: - ./envs/common-blockscout.env ports: - 4000:4000 + volumes: + - ./logs/:/app/logs/ + + smart-contract-verifier: + image: ghcr.io/blockscout/smart-contract-verifier:${SMART_CONTRACT_VERIFIER_DOCKER_TAG:-latest} + restart: always + container_name: 'smart-contract-verifier' + env_file: + - ./envs/common-smart-contract-verifier.env + ports: + - 8043:8043 + visualizer: + image: ghcr.io/blockscout/visualizer:${VISUALIZER_DOCKER_TAG:-latest} + restart: always + container_name: 'visualizer' + env_file: + - ./envs/common-visualizer.env + ports: + - 8050:8050 diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 03036522fc35..26d262489ab5 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -4,11 +4,12 @@ ETHEREUM_JSONRPC_HTTP_URL=http://host.docker.internal:8545/ DATABASE_URL=postgresql://postgres:@host.docker.internal:7432/blockscout?ssl=false ETHEREUM_JSONRPC_TRACE_URL=http://host.docker.internal:8545/ NETWORK= -SUBNETWORK=Awesome chain -LOGO=/images/blockscout_logo.svg -LOGO_FOOTER=/images/blockscout_logo.svg +SUBNETWORK=ALTLayer +LOGO=/images/altlayer_logo.png +LOGO_FOOTER=/images/altlayer_logo.png # ETHEREUM_JSONRPC_WS_URL= ETHEREUM_JSONRPC_TRANSPORT=http +ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false IPC_PATH= NETWORK_PATH=/ API_PATH=/ @@ -18,8 +19,7 @@ BLOCKSCOUT_PROTOCOL= # SECRET_KEY_BASE= # CHECK_ORIGIN= PORT=4000 -# COIN= -# COIN_NAME= +COIN_NAME=ALT # METADATA_CONTRACT= # VALIDATORS_CONTRACT= # KEYS_MANAGER_CONTRACT= @@ -28,6 +28,8 @@ PORT=4000 EMISSION_FORMAT=DEFAULT # CHAIN_SPEC_PATH= # SUPPLY_MODULE= +COIN=ALT +EXCHANGE_RATES_COIN= # EXCHANGE_RATES_SOURCE= # EXCHANGE_RATES_COINGECKO_COIN_ID= # EXCHANGE_RATES_COINGECKO_API_KEY= @@ -45,6 +47,7 @@ BLOCKSCOUT_VERSION= RELEASE_LINK= BLOCK_TRANSFORMER=base # GRAPHIQL_TRANSACTION= +# BLOCK_RANGES= # FIRST_BLOCK= # LAST_BLOCK= # TRACE_FIRST_BLOCK= @@ -75,8 +78,21 @@ DISABLE_WEBAPP=false DISABLE_READ_API=false DISABLE_WRITE_API=false DISABLE_INDEXER=false +DISABLE_REALTIME_INDEXER=false +DISABLE_TOKEN_INSTANCE_FETCHER=false INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=false INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false +# INDEXER_CATCHUP_BLOCKS_BATCH_SIZE= +# INDEXER_CATCHUP_BLOCKS_CONCURRENCY= +# INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE= +# INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY= +# INDEXER_COIN_BALANCES_BATCH_SIZE= +# INDEXER_COIN_BALANCES_CONCURRENCY= +# INDEXER_RECEIPTS_BATCH_SIZE= +# INDEXER_RECEIPTS_CONCURRENCY= +# TOKEN_ID_MIGRATION_FIRST_BLOCK= +# TOKEN_ID_MIGRATION_CONCURRENCY= +# TOKEN_ID_MIGRATION_BATCH_SIZE= # WEBAPP_URL= # API_URL= WOBSERVER_ENABLED=false @@ -103,7 +119,7 @@ CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN= ENABLE_SOURCIFY_INTEGRATION=false SOURCIFY_SERVER_URL= SOURCIFY_REPO_URL= -# CHAIN_ID= +CHAIN_ID= MAX_SIZE_UNLESS_HIDE_ARRAY=50 HIDE_BLOCK_MINER=false DISPLAY_TOKEN_ICONS=false @@ -112,10 +128,32 @@ TENDERLY_CHAIN_PATH= MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040 RE_CAPTCHA_SECRET_KEY= RE_CAPTCHA_CLIENT_KEY= -# JSON_RPC= +JSON_RPC= API_RATE_LIMIT=50 API_RATE_LIMIT_BY_KEY=50 API_RATE_LIMIT_BY_IP=50 API_RATE_LIMIT_WHITELISTED_IPS= API_RATE_LIMIT_STATIC_API_KEY= FETCH_REWARDS_WAY=trace_block +ENABLE_RUST_VERIFICATION_SERVICE=true +RUST_VERIFICATION_SERVICE_URL=http://host.docker.internal:8043/ +VISUALIZE_SOL2UML_ENABLED=true +VISUALIZE_SOL2UML_SERVICE_URL=http://host.docker.internal:8050/ +# DATABASE_READ_ONLY_API_URL= +# ACCOUNT_DATABASE_URL= +# ACCOUNT_POOL_SIZE= +# ACCOUNT_AUTH0_DOMAIN= +# ACCOUNT_AUTH0_CLIENT_ID= +# ACCOUNT_AUTH0_CLIENT_SECRET= +# ACCOUNT_AUTH0_LOGOUT_URL= +# ACCOUNT_AUTH0_LOGOUT_RETURN_URL= +# ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL= +# ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY= +# ACCOUNT_SENDGRID_API_KEY= +# ACCOUNT_SENDGRID_SENDER= +# ACCOUNT_SENDGRID_TEMPLATE= +ACCOUNT_CLOAK_KEY= +ACCOUNT_ENABLED=false +ACCOUNT_REDIS_URL=redis://redis_db:6379 +FOOTER_CHAT_LINK=http://discord.gg/altlayer +FOOTER_FORUM_LINK=https://forum.poa.network/c/blockscout diff --git a/docker-compose/envs/common-smart-contract-verifier.env b/docker-compose/envs/common-smart-contract-verifier.env new file mode 100644 index 000000000000..4fc6b18d117c --- /dev/null +++ b/docker-compose/envs/common-smart-contract-verifier.env @@ -0,0 +1,20 @@ +#SMART_CONTRACT_VERIFIER_DOCKER_TAG= +SMART_CONTRACT_VERIFIER__SERVER__ADDR=0.0.0.0:8043 + +SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED=true +SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR=/tmp/solidity-compilers +SMART_CONTRACT_VERIFIER__SOLIDITY__REFRESH_VERSIONS_SCHEDULE=0 0 * * * * * +SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/linux-amd64/list.json + +SMART_CONTRACT_VERIFIER__VYPER__ENABLED=false +SMART_CONTRACT_VERIFIER__VYPER__COMPILERS_DIR=/tmp/vyper-compilers +SMART_CONTRACT_VERIFIER__VYPER__FETCHER__LIST__LIST_URL=https://raw.githubusercontent.com/blockscout/solc-bin/main/vyper.list.json +SMART_CONTRACT_VERIFIER__VYPER__REFRESH_VERSIONS_SCHEDULE=0 0 * * * * * + +SMART_CONTRACT_VERIFIER__SOURCIFY__ENABLED=true +SMART_CONTRACT_VERIFIER__SOURCIFY__API_URL=https://sourcify.dev/server/ +SMART_CONTRACT_VERIFIER__SOURCIFY__VERIFICATION_ATTEMPTS=3 +SMART_CONTRACT_VERIFIER__SOURCIFY__REQUEST_TIMEOUT=10 + +SMART_CONTRACT_VERIFIER__METRICS__ENABLED=false +SMART_CONTRACT_VERIFIER__JAEGER__ENABLED=false diff --git a/docker-compose/envs/common-visualizer.env b/docker-compose/envs/common-visualizer.env new file mode 100644 index 000000000000..b4fd470849cb --- /dev/null +++ b/docker-compose/envs/common-visualizer.env @@ -0,0 +1 @@ +VISUALIZER__SERVER__GRPC__ENABLED=false diff --git a/docker/Dockerfile b/docker/Dockerfile index 605d9a986915..1ffd1c66e35e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,16 @@ -FROM bitwalker/alpine-elixir-phoenix:1.13 +FROM bitwalker/alpine-elixir-phoenix:1.13 AS builder + +WORKDIR /app -RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file qemu-x86_64 +RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file qemu-x86_64 jq -ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc -ENV GLIBC_VERSION=2.30-r0 +ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc \ + GLIBC_VERSION=2.30-r0 \ + PORT=4000 \ + MIX_ENV="prod" \ + SECRET_KEY_BASE="RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5" \ + PATH="$HOME/.cargo/bin:${PATH}" \ + RUSTFLAGS="-C target-feature=-crt-static" RUN set -ex && \ apk --update add libstdc++ curl ca-certificates && \ @@ -16,14 +23,17 @@ RUN set -ex && \ # Get Rust RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -ENV PATH="$HOME/.cargo/bin:${PATH}" -ENV RUSTFLAGS="-C target-feature=-crt-static" - -EXPOSE 4000 - -ENV PORT=4000 \ - MIX_ENV="prod" \ - SECRET_KEY_BASE="RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5" +ARG CACHE_EXCHANGE_RATES_PERIOD +ARG DISABLE_READ_API +ARG API_PATH +ARG NETWORK_PATH +ARG DISABLE_WEBAPP +ARG DISABLE_WRITE_API +ARG CACHE_ENABLE_TOTAL_GAS_USAGE_COUNTER +ARG WOBSERVER_ENABLED +ARG ADMIN_PANEL_ENABLED +ARG CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL +ARG SOCKET_ROOT # Cache elixir deps ADD mix.exs mix.lock ./ @@ -34,33 +44,34 @@ ADD apps/indexer/mix.exs ./apps/indexer/ RUN mix do deps.get, local.rebar --force, deps.compile -ADD . . - -ENV COIN='ALT' -RUN sed -i s/"POA"/"${COIN}"/g apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po; \ - sed -i "/msgid \"Ether\"/{n;s/msgstr \"\"/msgstr \"${COIN}\"/g}" apps/block_scout_web/priv/gettext/default.pot; \ - sed -i "/msgid \"Ether\"/{n;s/msgstr \"\"/msgstr \"${COIN}\"/g}" apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po; \ - sed -i "/msgid \"ETH\"/{n;s/msgstr \"\"/msgstr \"${COIN}\"/g}" apps/block_scout_web/priv/gettext/default.pot; \ - sed -i "/msgid \"ETH\"/{n;s/msgstr \"\"/msgstr \"${COIN}\"/g}" apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po; \ - -RUN sed -i "/msgid \"Chat (#blockscout)\"/{n;s/msgstr \"\"/msgstr \"Chat (#altlayer)\"/g}" apps/block_scout_web/priv/gettext/default.pot; \ - sed -i "/msgid \"Chat (#blockscout)\"/{n;s/msgstr \"\"/msgstr \"Chat (#altlayer)\"/g}" apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po; +COPY . . -ENV LOGO=/images/altlayer_logo.png +RUN sed -i "s/\"Chat (#blockscout)\"/\"Chat (#altlayer)\"/g" apps/block_scout_web/priv/gettext/default.pot; \ + sed -i "s/\"Chat (#blockscout)\"/\"Chat (#altlayer)\"/g" apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex; \ + sed -i "s/\"Chat (#blockscout)\"/\"Chat (#altlayer)\"/g" apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po; # Run forderground build and phoenix digest -RUN mix compile - -RUN npm install npm@latest +RUN mix compile && npm install npm@latest # Add blockscout npm deps RUN cd apps/block_scout_web/assets/ && \ npm install && \ npm run deploy && \ - cd - - -RUN cd apps/explorer/ && \ + cd /app/apps/explorer/ && \ npm install && \ - apk update && apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 + apk update && \ + apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 RUN mix phx.digest + +RUN mkdir -p /opt/release \ + && mix release blockscout \ + && mv _build/${MIX_ENV}/rel/blockscout /opt/release + +############################################################## +FROM bitwalker/alpine-elixir-phoenix:1.13 + +WORKDIR /app + +COPY --from=builder /opt/release/blockscout . +COPY --from=builder /app/apps/explorer/node_modules ./node_modules diff --git a/docker/Makefile b/docker/Makefile index f2e0d9a3bba0..7c420a771018 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -4,7 +4,7 @@ DOCKER_REPO := blockscout APP_NAME := blockscout BS_CONTAINER_IMAGE := $(DOCKER_REPO)/$(APP_NAME) BS_CONTAINER_NAME := blockscout -PG_CONTAINER_IMAGE := postgres:13.6 +PG_CONTAINER_IMAGE := postgres:14 PG_CONTAINER_NAME := db THIS_FILE = $(lastword $(MAKEFILE_LIST)) TAG := $(RELEASE_VERSION)-prerelease-$(shell git log -1 --pretty=format:"%h") @@ -52,6 +52,9 @@ endif ifdef ETHEREUM_JSONRPC_TRANSPORT BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_TRANSPORT=$(ETHEREUM_JSONRPC_TRANSPORT)' endif +ifdef ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=$(ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES)' +endif ifdef IPC_PATH BLOCKSCOUT_CONTAINER_PARAMS += -e 'IPC_PATH=$(IPC_PATH)' endif @@ -115,6 +118,9 @@ endif ifdef GRAPHIQL_TRANSACTION BLOCKSCOUT_CONTAINER_PARAMS += -e 'GRAPHIQL_TRANSACTION=$(GRAPHIQL_TRANSACTION)' endif +ifdef BLOCK_RANGES + BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCK_RANGES=$(BLOCK_RANGES)' +endif ifdef FIRST_BLOCK BLOCKSCOUT_CONTAINER_PARAMS += -e 'FIRST_BLOCK=$(FIRST_BLOCK)' endif @@ -172,6 +178,12 @@ endif ifdef DISABLE_INDEXER BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_INDEXER=$(DISABLE_INDEXER)' endif +ifdef DISABLE_REALTIME_INDEXER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_REALTIME_INDEXER=$(DISABLE_REALTIME_INDEXER)' +endif +ifdef DISABLE_TOKEN_INSTANCE_FETCHER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_TOKEN_INSTANCE_FETCHER=$(DISABLE_TOKEN_INSTANCE_FETCHER)' +endif ifdef WEBAPP_URL BLOCKSCOUT_CONTAINER_PARAMS += -e 'WEBAPP_URL=$(WEBAPP_URL)' endif @@ -211,6 +223,9 @@ endif ifdef CHECKSUM_FUNCTION BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECKSUM_FUNCTION=$(CHECKSUM_FUNCTION)' endif +ifdef EXCHANGE_RATES_COIN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_COIN=$(EXCHANGE_RATES_COIN)' +endif ifdef EXCHANGE_RATES_SOURCE BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_SOURCE=$(EXCHANGE_RATES_SOURCE)' endif @@ -358,46 +373,206 @@ endif ifdef API_RATE_LIMIT_WHITELISTED_IPS BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_WHITELISTED_IPS=$(API_RATE_LIMIT_WHITELISTED_IPS)' endif -ifdef COIN_NAME - BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_NAME=$(COIN_NAME)' -endif -ifdef INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER - BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=$(INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER)' -endif -ifdef INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER - BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=$(INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER)' -endif ifdef TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES=$(TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES)' endif ifdef COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES=$(COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES)' endif -ifdef INDEXER_MEMORY_LIMIT - BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_MEMORY_LIMIT=$(INDEXER_MEMORY_LIMIT)' -endif ifdef ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT=$(ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT)' endif ifdef FETCH_REWARDS_WAY BLOCKSCOUT_CONTAINER_PARAMS += -e 'FETCH_REWARDS_WAY=$(FETCH_REWARDS_WAY)' endif +ifdef FOOTER_CHAT_LINK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_CHAT_LINK=$(FOOTER_CHAT_LINK)' +endif +ifdef FOOTER_FORUM_LINK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_FORUM_LINK=$(FOOTER_FORUM_LINK)' +endif +ifdef FOOTER_GITHUB_LINK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOOTER_GITHUB_LINK=$(FOOTER_GITHUB_LINK)' +endif +ifdef NETWORK_ICON + BLOCKSCOUT_CONTAINER_PARAMS += -e 'NETWORK_ICON=$(NETWORK_ICON)' +endif +ifdef LOGO_TEXT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'LOGO_TEXT=$(LOGO_TEXT)' +endif +ifdef SHOW_TESTNET_LABEL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_TESTNET_LABEL=$(SHOW_TESTNET_LABEL)' +endif +ifdef TESTNET_LABEL_TEXT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TESTNET_LABEL_TEXT=$(TESTNET_LABEL_TEXT)' +endif +ifdef OTHER_EXPLORERS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'OTHER_EXPLORERS=$(OTHER_EXPLORERS)' +endif +ifdef CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST=$(CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST)' +endif +ifdef CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5 + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5=$(CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5)' +endif +ifdef CUSTOM_CONTRACT_ADDRESSES_CIRCLES + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CUSTOM_CONTRACT_ADDRESSES_CIRCLES=$(CUSTOM_CONTRACT_ADDRESSES_CIRCLES)' +endif +ifdef HEALTHY_BLOCKS_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'HEALTHY_BLOCKS_PERIOD=$(HEALTHY_BLOCKS_PERIOD)' +endif +ifdef EXCHANGE_RATES_FETCH_BTC_VALUE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXCHANGE_RATES_FETCH_BTC_VALUE=$(EXCHANGE_RATES_FETCH_BTC_VALUE)' +endif +ifdef ENABLE_TXS_STATS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ENABLE_TXS_STATS=$(ENABLE_TXS_STATS)' +endif +ifdef INDEXER_MEMORY_LIMIT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_MEMORY_LIMIT=$(INDEXER_MEMORY_LIMIT)' +endif +ifdef INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=$(INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER)' +endif +ifdef INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=$(INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER)' +endif +ifdef INDEXER_DISABLE_BLOCK_REWARD_FETCHER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_BLOCK_REWARD_FETCHER=$(INDEXER_DISABLE_BLOCK_REWARD_FETCHER)' +endif +ifdef INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER=$(INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER)' +endif +ifdef INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER=$(INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER)' +endif +ifdef INDEXER_DISABLE_EMPTY_BLOCK_SANITIZER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_DISABLE_EMPTY_BLOCK_SANITIZER=$(INDEXER_DISABLE_EMPTY_BLOCK_SANITIZER)' +endif +ifdef INDEXER_CATCHUP_BLOCKS_BATCH_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_CATCHUP_BLOCKS_BATCH_SIZE=$(INDEXER_CATCHUP_BLOCKS_BATCH_SIZE)' +endif +ifdef INDEXER_CATCHUP_BLOCKS_CONCURRENCY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_CATCHUP_BLOCKS_CONCURRENCY=$(INDEXER_CATCHUP_BLOCKS_CONCURRENCY)' +endif +ifdef INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE=$(INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE)' +endif +ifdef INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY=$(INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY)' +endif +ifdef INDEXER_COIN_BALANCES_BATCH_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_BATCH_SIZE=$(INDEXER_RECEIPTS_BATCH_SIZE)' +endif +ifdef INDEXER_COIN_BALANCES_CONCURRENCY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_CONCURRENCY=$(INDEXER_RECEIPTS_CONCURRENCY)' +endif +ifdef INDEXER_RECEIPTS_BATCH_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_BATCH_SIZE=$(INDEXER_RECEIPTS_BATCH_SIZE)' +endif +ifdef INDEXER_RECEIPTS_CONCURRENCY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_RECEIPTS_CONCURRENCY=$(INDEXER_RECEIPTS_CONCURRENCY)' +endif +ifdef INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE=$(INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE)' +endif +ifdef TOKEN_ID_MIGRATION_FIRST_BLOCK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE=$(INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE)' +endif +ifdef TOKEN_ID_MIGRATION_CONCURRENCY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE=$(INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE)' +endif +ifdef TOKEN_ID_MIGRATION_BATCH_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE=$(INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE)' +endif +ifdef SECRET_KEY_BASE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SECRET_KEY_BASE=$(SECRET_KEY_BASE)' +endif +ifdef PORT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'PORT=$(PORT)' +endif +ifdef DATABASE_READ_ONLY_API_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DATABASE_READ_ONLY_API_URL=$(DATABASE_READ_ONLY_API_URL)' +endif +ifdef POOL_SIZE_API + BLOCKSCOUT_CONTAINER_PARAMS += -e 'POOL_SIZE_API=$(POOL_SIZE_API)' +endif +ifdef ENABLE_RUST_VERIFICATION_SERVICE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ENABLE_RUST_VERIFICATION_SERVICE=$(ENABLE_RUST_VERIFICATION_SERVICE)' +endif +ifdef RUST_VERIFICATION_SERVICE_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RUST_VERIFICATION_SERVICE_URL=$(RUST_VERIFICATION_SERVICE_URL)' +endif +ifdef ACCOUNT_ENABLED + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_ENABLED=$(ACCOUNT_ENABLED)' +endif +ifdef ACCOUNT_REDIS_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_REDIS_URL=$(ACCOUNT_REDIS_URL)' +endif +ifdef COIN_NAME + BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_NAME=$(COIN_NAME)' +endif +ifdef ACCOUNT_AUTH0_DOMAIN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_DOMAIN=$(ACCOUNT_AUTH0_DOMAIN)' +endif +ifdef ACCOUNT_AUTH0_CLIENT_ID + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_CLIENT_ID=$(ACCOUNT_AUTH0_CLIENT_ID)' +endif +ifdef ACCOUNT_AUTH0_CLIENT_SECRET + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_CLIENT_SECRET=$(ACCOUNT_AUTH0_CLIENT_SECRET)' +endif +ifdef ACCOUNT_AUTH0_LOGOUT_RETURN_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_LOGOUT_RETURN_URL=$(ACCOUNT_AUTH0_LOGOUT_RETURN_URL)' +endif +ifdef ACCOUNT_AUTH0_LOGOUT_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_AUTH0_LOGOUT_URL=$(ACCOUNT_AUTH0_LOGOUT_URL)' +endif +ifdef ACCOUNT_SENDGRID_API_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_SENDGRID_API_KEY=$(ACCOUNT_SENDGRID_API_KEY)' +endif +ifdef ACCOUNT_SENDGRID_SENDER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_SENDGRID_SENDER=$(ACCOUNT_SENDGRID_SENDER)' +endif +ifdef ACCOUNT_SENDGRID_TEMPLATE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_SENDGRID_TEMPLATE=$(ACCOUNT_SENDGRID_TEMPLATE)' +endif +ifdef ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL=$(ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL)' +endif +ifdef ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY=$(ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY)' +endif +ifdef ACCOUNT_DATABASE_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_DATABASE_URL=$(ACCOUNT_DATABASE_URL)' +endif +ifdef ACCOUNT_POOL_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_POOL_SIZE=$(ACCOUNT_POOL_SIZE)' +endif +ifdef ACCOUNT_CLOAK_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_CLOAK_KEY=$(ACCOUNT_CLOAK_KEY)' +endif +ifdef VISUALIZE_SOL2UML_ENABLED + BLOCKSCOUT_CONTAINER_PARAMS += -e 'VISUALIZE_SOL2UML_ENABLED=$(VISUALIZE_SOL2UML_ENABLED)' +endif +ifdef VISUALIZE_SOL2UML_SERVICE_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'VISUALIZE_SOL2UML_SERVICE_URL=$(VISUALIZE_SOL2UML_SERVICE_URL)' +endif + HAS_BLOCKSCOUT_IMAGE := $(shell docker images | grep -sw "${BS_CONTAINER_IMAGE} ") -build: +build: @echo "==> Checking for blockscout image $(BS_CONTAINER_IMAGE)" ifdef HAS_BLOCKSCOUT_IMAGE @echo "==> Image exist. Using $(BS_CONTAINER_IMAGE)" else @echo "==> No image found, trying to build one..." - @docker build --build-arg COIN="$(COIN)" -f ./Dockerfile -t $(BS_CONTAINER_IMAGE) ../ + @docker build -f ./Dockerfile -t $(BS_CONTAINER_IMAGE) ../ endif migrate_only: @echo "==> Running migrations" @docker run --rm \ $(BLOCKSCOUT_CONTAINER_PARAMS) \ - $(BS_CONTAINER_IMAGE) /bin/sh -c "echo $$MIX_ENV && mix do ecto.create, ecto.migrate" + $(BS_CONTAINER_IMAGE) /bin/sh -c "echo $$MIX_ENV && ./bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\"" migrate: build postgres @$(MAKE) -f $(THIS_FILE) migrate_only @@ -433,12 +608,12 @@ else endif endif -start: build postgres +start: build postgres @echo "==> Starting blockscout" @docker run --rm --name $(BS_CONTAINER_NAME) \ $(BLOCKSCOUT_CONTAINER_PARAMS) \ -p 4000:4000 \ - $(BS_CONTAINER_IMAGE) /bin/sh -c "mix phx.server" + $(BS_CONTAINER_IMAGE) /bin/sh -c "./bin/blockscout start" BS_STARTED := $(shell docker ps --no-trunc --filter name=^/${BS_CONTAINER_NAME}$) stop: diff --git a/mix.exs b/mix.exs index 7cacca369098..90bd3dcd3c8d 100644 --- a/mix.exs +++ b/mix.exs @@ -5,13 +5,13 @@ defmodule BlockScout.Mixfile do def project do [ - app: :block_scout, + # app: :block_scout, # aliases: aliases(config_env()), - version: "4.1.3", + version: "4.1.8", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), - elixir: "~> 1.12", + elixir: "~> 1.13", preferred_cli_env: [ credo: :test, dialyzer: :test @@ -24,7 +24,9 @@ defmodule BlockScout.Mixfile do ethereum_jsonrpc: :permanent, explorer: :permanent, indexer: :permanent - ] + ], + steps: [:assemble, ©_prod_runtime_config/1], + validate_compile_env: false ] ] ] @@ -32,6 +34,22 @@ defmodule BlockScout.Mixfile do ## Private Functions + defp copy_prod_runtime_config(%Mix.Release{path: path} = release) do + File.mkdir_p!(Path.join([path, "config", "runtime"])) + File.cp!(Path.join(["config", "runtime", "prod.exs"]), Path.join([path, "config", "runtime", "prod.exs"])) + File.mkdir_p!(Path.join([path, "apps", "explorer", "config", "prod"])) + + File.cp_r!( + Path.join(["apps", "explorer", "config", "prod"]), + Path.join([path, "apps", "explorer", "config", "prod"]) + ) + + File.mkdir_p!(Path.join([path, "apps", "indexer", "config", "prod"])) + File.cp_r!(Path.join(["apps", "indexer", "config", "prod"]), Path.join([path, "apps", "indexer", "config", "prod"])) + + release + end + defp dialyzer() do [ plt_add_deps: :transitive, @@ -74,10 +92,11 @@ defmodule BlockScout.Mixfile do # and cannot be accessed from applications inside the apps folder defp deps do [ + {:prometheus_ex, git: "https://github.com/lanodan/prometheus.ex", branch: "fix/elixir-1.14", override: true}, {:absinthe_plug, git: "https://github.com/blockscout/absinthe_plug.git", tag: "1.5.3", override: true}, - {:tesla, "~> 1.3.3"}, + {:tesla, "~> 1.4.4"}, # Documentation - {:ex_doc, "~> 0.28.2", only: :dev, runtime: false}, + {:ex_doc, "~> 0.29.0", only: :dev, runtime: false}, {:number, "~> 1.0.3"} ] end diff --git a/mix.lock b/mix.lock index 80fa4d16cc67..46186a271b12 100644 --- a/mix.lock +++ b/mix.lock @@ -1,76 +1,81 @@ %{ - "absinthe": {:hex, :absinthe, "1.6.5", "b7bdb507824412c447806e08d80c8f231d51a1b40b1400e5d01bd7faf77ffda1", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a4f4ed3808467f1795baadbba24f1355e287b64e6b41ec0cfeb0713575ecb7a9"}, + "absinthe": {:hex, :absinthe, "1.7.0", "36819e7b1fd5046c9c734f27fe7e564aed3bda59f0354c37cd2df88fd32dd014", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "566a5b5519afc9b29c4d367f0c6768162de3ec03e9bf9916f9dc2bcbe7c09643"}, "absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.2", "e607b438db900049b9b3760f8ecd0591017a46122fffed7057bf6989020992b5", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d36918925c380dc7d2ed7d039c9a3b4182ec36723f7417a68745ade5aab22f8d"}, "absinthe_plug": {:git, "https://github.com/blockscout/absinthe_plug.git", "c435d43f316769e1beee1dbe500b623124c96785", [tag: "1.5.3"]}, - "absinthe_relay": {:hex, :absinthe_relay, "1.5.1", "adf298e77cf83d52bae1d7dc1579146bf9b893fcaa7b556d62e81a8c6f997514", [:mix], [{:absinthe, "~> 1.5.0 or ~> 1.6.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1fd2a3559f8472c5bac5778c8b87ae5a5d7f89b594eba26b684ce1d0345a910a"}, + "absinthe_relay": {:hex, :absinthe_relay, "1.5.2", "cfb8aed70f4e4c7718d3f1c212332d2ea728f17c7fc0f68f1e461f0f5f0c4b9a", [:mix], [{:absinthe, "~> 1.5.0 or ~> 1.6.0 or ~> 1.7.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "0587ee913afa31512e1457a5064ee88427f8fe7bcfbeeecd41c71d9cff0b62b6"}, "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, - "bcrypt_elixir": {:hex, :bcrypt_elixir, "1.1.1", "6b5560e47a02196ce5f0ab3f1d8265db79a23868c137e973b27afef928ed8006", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "10f658be786bd2daaadcd45cc5b598da01d5bbc313da4d0e3efb2d6a511d896d"}, - "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "d8b3f1720073413c36a21e56a1d1112a4d67a9ad0ec900437efed08b39e515b2"}, - "benchee_csv": {:hex, :benchee_csv, "0.8.0", "0ca094677d6e2b2f601b7ee7864b754789ef9d24d079432e5e3d6f4fb83a4d80", [:mix], [{:benchee, "~> 0.12", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "74f5f5b5fe50f05ee29023d35a4d7d543230d63fa15c67076a36ad46e2b040d8"}, - "binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], [], "hexpm", "ee1e9ebcab703a4e24db554957fbb540642fe9327eb9e295cb3f07dd7c11ddb2"}, - "briefly": {:git, "https://github.com/CargoSense/briefly.git", "25942fba9cad46aaa870ba248c101ffee321ec9b", []}, - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm", "5a1dc855dfcc86160458c7a70d25f65d498bd8012bd4c06a8d3baa368dda3c45"}, + "bamboo": {:hex, :bamboo, "2.2.0", "f10a406d2b7f5123eb1f02edfa043c259db04b47ab956041f279eaac776ef5ce", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8c3b14ba7d2f40cb4be04128ed1e2aff06d91d9413d38bafb4afccffa3ade4fc"}, + "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, + "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, + "benchee_csv": {:hex, :benchee_csv, "1.0.0", "0b3b9223290bfcb8003552705bec9bcf1a89b4a83b70bd686e45295c264f3d16", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "cdefb804c021dcf7a99199492026584be9b5a21d6644ac0d01c81c5d97c520d5"}, + "briefly": {:git, "https://github.com/CargoSense/briefly.git", "1dd66ee19ca84ed60f4eca47fee59227ba960fb7", []}, + "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bureaucrat": {:hex, :bureaucrat, "0.2.9", "d98e4d2b9bdbf22e4a45c2113ce8b38b5b63278506c6ff918e3b943a4355d85b", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "111c8dd84382a62e1026ae011d592ceee918553e5203fe8448d9ba6ccbdfff7d"}, + "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "cbor": {:hex, :cbor, "1.0.0", "35d33a26f6420ce3d2d01c0b1463a748b34c537d5609fc40116daf3666700d36", [:mix], [], "hexpm", "cc5e21e0fa5a0330715a3806c67bc294f8b65d07160f751b5bd6058bed1962ac"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, - "cldr_utils": {:hex, :cldr_utils, "2.14.0", "edcef8dd2654b93d84a90087f3536ffabf9c9d82b34ff82bc9ca54c0668b3a4a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "6903356ff6988342a29b90637eece4ca98a4ed2b9759c22233d3474ade57645a"}, + "cldr_utils": {:hex, :cldr_utils, "2.19.1", "5a7bcd2f2fd432c548e494e850bba8a9e838f1b10202f682ea1d9809d74eff31", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "fbd10f79363e70f3d893ab21e195f444ca87c2c80120b5911761491da4489620"}, + "cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"}, + "cloak_ecto": {:hex, :cloak_ecto, "1.2.0", "e86a3df3bf0dc8980f70406bcb0af2858bac247d55494d40bc58a152590bd402", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "8bcc677185c813fe64b786618bd6689b1707b35cd95acaae0834557b15a0c62f"}, + "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm", "d8700a0ca4dbb616c22c9b3f6dd539d88deaafec3efe66869d6370c9a559b3e9"}, + "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "con_cache": {:hex, :con_cache, "1.0.0", "6405e2bd5d5005334af72939432783562a8c35a196c2e63108fe10bb97b366e6", [:mix], [], "hexpm", "4d1f5cb1a67f3c1a468243dc98d10ac83af7f3e33b7e7c15999dc2c9bc0a551e"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, - "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"}, + "cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, - "credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"}, - "csv": {:hex, :csv, "2.4.1", "50e32749953b6bf9818dbfed81cf1190e38cdf24f95891303108087486c5925e", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "54508938ac67e27966b10ef49606e3ad5995d665d7fc2688efb3eab1307c9079"}, - "dataloader": {:hex, :dataloader, "1.0.9", "8fb981e327fa692f741ab283ed93790203a6f6d412800f0f4f1531372e1dbf15", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f8b7566c8dda46f53bdb336fd02f03f00bf58aeb6cc0f139ccdfd6f99d265a7"}, - "db_connection": {:hex, :db_connection, "2.4.0", "d04b1b73795dae60cead94189f1b8a51cc9e1f911c234cc23074017c43c031e5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad416c21ad9f61b3103d254a71b63696ecadb6a917b36f563921e0de00d7d7c8"}, + "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, + "csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"}, + "dataloader": {:hex, :dataloader, "1.0.10", "a42f07641b1a0572e0b21a2a5ae1be11da486a6790f3d0d14512d96ff3e3bbe9", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "54cd70cec09addf4b2ace14cc186a283a149fd4d3ec5475b155951bf33cd963f"}, + "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, - "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm", "e3bf435a54ed27b0ba3a01eb117ae017988804e136edcbe8a6a14c310daa966e"}, - "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, - "earmark": {:hex, :earmark, "1.3.5", "0db71c8290b5bc81cb0101a2a507a76dca659513984d683119ee722828b424f6", [:mix], [], "hexpm", "762b999fd414fb41e297944228aa1de2cd4a3876a07f968c8b11d1e9a2190d07"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.20", "89970db71b11b6b89759ce16807e857df154f8df3e807b2920a8c39834a9e5cf", [:mix], [], "hexpm", "1eb0d2dabeeeff200e0d17dc3048a6045aab271f73ebb82e416464832eb57bdd"}, - "ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"}, - "ecto_sql": {:hex, :ecto_sql, "3.7.1", "8de624ef50b2a8540252d8c60506379fbbc2707be1606853df371cf53df5d053", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b42a32e2ce92f64aba5c88617891ab3b0ba34f3f3a503fa20009eae1a401c81"}, - "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, + "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, + "digital_token": {:hex, :digital_token, "0.4.0", "2ad6894d4a40be8b2890aad286ecd5745fa473fa5699d80361a8c94428edcd1f", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a178edf61d1fee5bb3c34e14b0f4ee21809ee87cade8738f87337e59e5e66e26"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, + "ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"}, + "ecto_sql": {:hex, :ecto_sql, "3.9.1", "9bd5894eecc53d5b39d0c95180d4466aff00e10679e13a5cfa725f6f85c03c22", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fd470a4fff2e829bbf9dcceb7f3f9f6d1e49b4241e802f614de6b8b67c51118"}, + "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_abi": {:hex, :ex_abi, "0.5.11", "a53307cf796231bf068a9941d57fbcb8654e72042c2a113a49f08dfb248875fb", [:mix], [{:ex_keccak, "~> 0.4.0", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e128577740bdc0f05ed6841cbb1c88bb80377613970ed870fa052b780870655a"}, - "ex_cldr": {:hex, :ex_cldr, "2.19.1", "6bd81c826202d08420ebf7174306d277a8c4093c3c32c188ac9a636927f27c7e", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:cldr_utils, "~> 2.12", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "5541261dd2915b7c9fb1408b1cfe9075657515e4b348ccb921e45e149dea6b11"}, - "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.8.0", "b2ecc94e9fa4b8ec07614830f4d6e811e5df5e7679c6d2be92f4fe4f31184913", [:mix], [{:ex_cldr, "~> 2.18", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "a39780667b73bfd3d2bd08e61981bca23a97912b86f3236042850ecb062f48eb"}, - "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.7.0", "86264f509ada404afc9d469faf58ad78a9cbba4b728776eb218ee1bf9a9396a2", [:mix], [{:ex_cldr_numbers, "~> 2.16", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ec9f55af45aa628930900e84d09bab50ee61841ad974aeb8fd51f627a9685353"}, - "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.16.1", "aea1cecbf19dfb6ac82658d05685015172866cf1a4ce0778f58157442ccf015d", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.18", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.8", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ba7a6f9f50aaa7759d05e508a333747cf670c823b2857780b025f3c421f4f1d3"}, - "ex_cldr_units": {:hex, :ex_cldr_units, "2.8.1", "f3fc6da7ff9795f70df16a9c8616df801dbc91831e2c4b994973d7199139b721", [:mix], [{:cldr_utils, "~> 2.6", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.13", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.2", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.12", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e2e0f5cebdd94dc75142a8cc2fcaec3ad6de5c1501adfe55369caeb4115e7b75"}, - "ex_doc": {:hex, :ex_doc, "0.28.2", "e031c7d1a9fc40959da7bf89e2dc269ddc5de631f9bd0e326cbddf7d8085a9da", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "51ee866993ffbd0e41c084a7677c570d0fc50cb85c6b5e76f8d936d9587fa719"}, - "ex_json_schema": {:hex, :ex_json_schema, "0.6.2", "de23d80478215987469c81688208fe0ff440ee0e0e6ae2268fcadbb2ff35df9d", [:mix], [], "hexpm", "2f25c57e919ffc5d6b02f2f130548284342dd6c3e99555ee0beeb9f2d2366a96"}, - "ex_keccak": {:hex, :ex_keccak, "0.4.0", "4eb8620c8a20a546e2d297b5ce3de150a90db0fdc4ba1dd88c854ace9ee47603", [:mix], [{:rustler, "~> 0.24", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "209ec5591d3cf79f9bdcdedf39c3d7a1fb208321e2b6de2660801117ae386f10"}, + "ex_abi": {:hex, :ex_abi, "0.5.16", "735f14937bc3c8fd53c38f02936ef8bf93d26a0b999cb0230b105d901530acaf", [:mix], [{:ex_keccak, "~> 0.6.0", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "82ee815f438c5d29ddc3e151a23a9eb5e906f3472cc6f5005b6f5a7f37332efe"}, + "ex_cldr": {:hex, :ex_cldr, "2.34.0", "50385e1445f33537bea7d24ca7525aa849ccabb3cf88ac41a6d183693116aba7", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:cldr_utils, "~> 2.18", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "d026df7d580424ab8daf0d908fd38d3c12e0a63962b495b7faeabd66ab072588"}, + "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.14.2", "354b48134faa011d58ae2e89be69b7de607d81fcc74c7ac684c5fb77b20186f5", [:mix], [{:ex_cldr, "~> 2.27", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "c970533103cdc97b1dedc2fead2209c0f5ae0aee0f1e504fdea5be5ee1466cd1"}, + "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.0", "4d4c9877da2d0417fd832907d69974e8328969f75fafc79b05ccf85f549f6281", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "adc040cde7b97f7fd7c0b35dd69ddb6fcf607303ae6355bb1851deae1f8b0652"}, + "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.28.0", "506f5d36a2b72a21bbcb6e55dfdc5c3ff7f1c07d65e516461125158d10661beb", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "83342ff668aedf3aa5c54b048ce1da0f91317b6596b14880a5f87d45cd1c49d2"}, + "ex_cldr_units": {:hex, :ex_cldr_units, "3.15.0", "3a834dfaf4daa0723cac165d528eacdbc3f9daec580f817b2847007fe07afdca", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.28", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:ratio, "~> 2.4", [hex: :ratio, repo: "hexpm", optional: false]}], "hexpm", "bac7c3f6042482869dd67445adddaec2c263f561a8c2035eac7bd5f9d5ae1691"}, + "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, + "ex_json_schema": {:hex, :ex_json_schema, "0.9.2", "c9a42e04e70cd70eb11a8903a22e8ec344df16edef4cb8e6ec84ed0caffc9f0f", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "4854329cb352b6c01c4c4b8dbfb3be14dc5bea19ea13e0eafade4ff22ba55224"}, + "ex_keccak": {:hex, :ex_keccak, "0.6.0", "0e1f8974dd6630dd4fb0b64f9eabbceeffb9675da3ab95dea653798365802cf4", [:mix], [{:rustler, "~> 0.26", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "84b20cfe6a063edab311b2c8ff8b221698c84cbd5fbdba059e51636540142538"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, - "ex_rlp": {:hex, :ex_rlp, "0.5.3", "9055bddade545ee3e734aaad62c4b4d08211834da3beb43ae269b75785909e5e", [:mix], [], "hexpm", "a755a5f8f9f66079f3ecbe021536b949077fac0df963d9e59a20321bab28722d"}, + "ex_rlp": {:hex, :ex_rlp, "0.5.5", "92af4cc1ded4e2694428fe3adc14606b6f54f840bda1b6463df46c61cb6fed2c", [:mix], [], "hexpm", "22929df57e490e4312350d9914f5810735845e7e92a7ace8a8895d730b4a53f6"}, "ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm", "66d4fe75285948f2d1e69c2a5ddd651c398c813574f8d36a9eef11dc20356ef6"}, "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, - "exvcr": {:hex, :exvcr, "0.13.2", "e17fd3ee3a341f41a3aa65a3ce73a339759a9d0658f83782492c6e9b6cf9daa4", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.8.0", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "17f41a533d14f582fe6b5f83214f058cf5ba77c6a7bc15bc53a9ea1827d92d96"}, + "exvcr": {:hex, :exvcr, "0.13.4", "68efca5ae04a909b29a9e137338a7033642898033c7a938a5faec545bfc5a38e", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "42920a59bdeef34001f8c2305a57d68b29d8a2e7aa1877bb35a75034b9f9904a"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "floki": {:hex, :floki, "0.32.1", "dfe3b8db3b793939c264e6f785bca01753d17318d144bd44b407fb3493acaa87", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "d4b91c713e4a784a3f7b1e3cc016eefc619f6b1c3898464222867cafd3c681a3"}, - "flow": {:hex, :flow, "0.15.0", "503717c0e367b5713336181d5305106840f64abbad32c75d7af5ef1bb0908e38", [:mix], [{:gen_stage, "~> 0.14.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "d7ecbd4dd38a188494bc996d5014ef8335f436a0b262140a1f6441ae94714581"}, - "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm", "8453e2289d94c3199396eb517d65d6715ef26bcae0ee83eb5ff7a84445458d76"}, - "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, + "floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"}, + "flow": {:hex, :flow, "1.2.0", "515e03aa3d056cecc3e3f1e80f6ca4bbf5f45b13c88dee5db880b2f3f24f1caa", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "1b45bfc8a9202c5ec80b077c21df133561e56c56189ba4082dddccb6b5762525"}, + "gen_stage": {:hex, :gen_stage, "1.1.2", "b1656cd4ba431ed02c5656fe10cb5423820847113a07218da68eae5d6a260c23", [:mix], [], "hexpm", "9e39af23140f704e2b07a3e29d8f05fd21c2aaf4088ff43cb82be4b9e3148d02"}, + "gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, - "hammer": {:hex, :hammer, "6.0.0", "72ec6fff10e9d63856968988a22ee04c4d6d5248071ddccfbda50aa6c455c1d7", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "d8e1ec2e534c4aae508b906759e077c3c1eb3e2b9425235d4b7bbab0b016210a"}, + "hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, - "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, + "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, + "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, - "junit_formatter": {:hex, :junit_formatter, "3.3.0", "bd7914d92885f7cf949dbe1dc6bacf76badfb2c1f5f7b3f9433c20e5b6ec42c8", [:mix], [], "hexpm", "4d040410925324b155ae4c7d41e884a0cdebe53b917bee4f22adf152e987a666"}, + "junit_formatter": {:hex, :junit_formatter, "3.3.1", "c729befb848f1b9571f317d2fefa648e9d4869befc4b2980daca7c1edc468e40", [:mix], [], "hexpm", "761fc5be4b4c15d8ba91a6dafde0b2c2ae6db9da7b8832a55b5a1deb524da72b"}, "libsecp256k1": {:hex, :libsecp256k1, "0.1.10", "d27495e2b9851c7765129b76c53b60f5e275bd6ff68292c50536bf6b8d091a4d", [:make, :mix], [{:mix_erlang_tasks, "0.1.0", [hex: :mix_erlang_tasks, repo: "hexpm", optional: false]}], "hexpm", "09ea06239938571124f7f5a27bc9ac45dfb1cfc2df40d46ee9b59c3d51366652"}, - "logger_file_backend": {:hex, :logger_file_backend, "0.0.12", "5afaa76a0cb6123cd19900c0f414044cfc46c24c6a1b80842a9b0e7f6c755e57", [:mix], [], "hexpm", "7335cc4e186a3804f9d3651f2fb42243a11748f1e384421bdd17623ed53fed79"}, + "logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, - "math": {:hex, :math, "0.3.1", "616b2644131b3fd2f5281c07e2c3d03b91bff220c6dde9f29e00a2149307de51", [:mix], [], "hexpm", "41e586b594e8d6891eae4ecca4f7d951ecc74824cc39ce9cb277e5fbba8de2b7"}, + "math": {:hex, :math, "0.7.0", "12af548c3892abf939a2e242216c3e7cbfb65b9b2fe0d872d05c6fb609f8127b", [:mix], [], "hexpm", "7987af97a0c6b58ad9db43eb5252a49fc1dfe1f6d98f17da9282e297f594ebc2"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, @@ -78,30 +83,31 @@ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimetype_parser": {:hex, :mimetype_parser, "0.1.3", "628ac9fe56aa7edcedb534d68397dd66674ab82493c8ebe39acb9a19b666099d", [:mix], [], "hexpm", "7d8f80c567807ce78cd93c938e7f4b0a20b1aaaaab914bf286f68457d9f7a852"}, "mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm", "95d2839c422c482a70c08a8702da8242f86b773f8ab6e8602a4eb72da8da04ed"}, - "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm", "b93e2b1e564bdbadfecc297277f9e6d0902da645b417d6c9210f6038ac63489a"}, "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"}, - "mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"}, - "msgpax": {:hex, :msgpax, "2.2.4", "7b3790ef684089076b63c0f08c2f4b079c6311daeb006b69e4ed2bf67518291e", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b351b6d992d79624a8430a99d21a41b36b1b90edf84326a294e9f4a2de11f089"}, - "nimble_csv": {:hex, :nimble_csv, "1.1.0", "b1dba4a86be9e03065c9de829050468e591f569100332db949e7ce71be0afc25", [:mix], [], "hexpm", "e986755bc302832cac429be6deda0fc9d82d3c82b47abefb68b3c17c9d949a3f"}, + "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, + "msgpax": {:hex, :msgpax, "2.3.0", "14f52ad249a3f77b5e2d59f6143e6c18a6e74f34666989e22bac0a465f9835cc", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "65c36846a62ed5615baf7d7d47babb6541313a6c0b6d2ff19354bd518f52df7e"}, + "nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "number": {:hex, :number, "1.0.3", "932c8a2d478a181c624138958ca88a78070332191b8061717270d939778c9857", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "dd397bbc096b2ca965a6a430126cc9cf7b9ef7421130def69bcf572232ca0f18"}, + "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, + "oauth2": {:hex, :oauth2, "2.0.1", "70729503e05378697b958919bb2d65b002ba6b28c8112328063648a9348aaa3f", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "c64e20d4d105bcdbcbe03170fb530d0eddc3a3e6b135a87528a22c8aecf74c52"}, "optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm", "1a06ea6a653120226b35b283a1cd10039550f2c566edcdec22b29316d73640fd"}, - "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"}, + "parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "phoenix": {:hex, :phoenix, "1.5.13", "d4e0805ec0973bed80d67302631130fb47d75b1a0b7335a0b23c4432b6ce55ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a7c4f1900e6e60bb60ae6680e48418e3f7c360d58bcb9f812487b6d0d281a0f"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, - "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.15.10", "2809dee1b1d76f7cbabe570b2a9285c2e7b41be60cf792f5f2804a54b838a067", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1560ca427542f6b213f8e281633ae1a3b31cdbcd84ebd7f50628765b8f6132be"}, - "prometheus": {:hex, :prometheus, "4.8.1", "fa76b152555273739c14b06f09f485cf6d5d301fe4e9d31b7ff803d26025d7a0", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "6edfbe928d271c7f657a6f2c46258738086584bd6cae4a000b8b9a6009ba23a5"}, + "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, + "prometheus": {:hex, :prometheus, "4.9.1", "ecf9ccf0fdd0fefb13b19f5216aff8b4bdc852171f5c79133bd998ce8210cf65", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "d75e80d7b2c1be6bf296e211e806e939ae3d9e0428f45b4caad1817f028213d3"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, - "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, + "prometheus_ex": {:git, "https://github.com/lanodan/prometheus.ex", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "prometheus_process_collector": {:hex, :prometheus_process_collector, "1.6.0", "b169e224337497cd858da16f9361edabc5931b9d12201a97ee15d88ef5a6fcaa", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "e9cd9846f204de7a04863f56308d8d1193bec714210bf6374d9d4fc088d2896d"}, @@ -109,22 +115,27 @@ "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, "que": {:hex, :que, "0.10.1", "788ed0ec92ed69bdf9cfb29bf41a94ca6355b8d44959bd0669cf706e557ac891", [:mix], [{:ex_utils, "~> 0.1.6", [hex: :ex_utils, repo: "hexpm", optional: false]}, {:memento, "~> 0.3.0", [hex: :memento, repo: "hexpm", optional: false]}], "hexpm", "a737b365253e75dbd24b2d51acc1d851049e87baae08cd0c94e2bc5cd65088d5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "remote_ip": {:hex, :remote_ip, "1.0.0", "3d7fb45204a5704443f480cee9515e464997f52c35e0a60b6ece1f81484067ae", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9e9fcad4e50c43b5234bb6a9629ed6ab223f3ed07147bd35470e4ee5c8caf907"}, - "rustler": {:hex, :rustler, "0.24.0", "b8362a2fee1c9d2c7373b0bfdc98f75bbc02864efcec50df173fe6c4f72d4cc4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "2773167fca68a6525822ad977b41368ea3c2af876c42ebaa7c9d6bb69b67f1ce"}, + "ratio": {:hex, :ratio, "2.4.2", "c8518f3536d49b1b00d88dd20d49f8b11abb7819638093314a6348139f14f9f9", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "441ef6f73172a3503de65ccf1769030997b0d533b1039422f1e5e0e0b4cbf89e"}, + "redix": {:hex, :redix, "1.2.0", "0d7eb3ccb7b82c461a6ea28b65c2c04486093d816dd6d901a09164800e004df1", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e1e0deb14599da07c77e66956a12863e85ee270ada826804a0ba8e61657e22a3"}, + "remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"}, + "rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, - "spandex": {:hex, :spandex, "3.0.3", "91aa318f3de696bb4d931adf65f7ebdbe5df25cccce1fe8fd376a44c46bcf69b", [:mix], [{:decorator, "~> 1.2", [hex: :decorator, repo: "hexpm", optional: true]}, {:optimal, "~> 0.3.3", [hex: :optimal, repo: "hexpm", optional: false]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e3e6c319d0ab478ddc9a39102a727a410c962b4d51c0932c72279b86d3b17044"}, - "spandex_datadog": {:hex, :spandex_datadog, "1.1.0", "8c84e2f6c4067edc2e920dd79242f7bb0d6403652a7e9bc42109007f76b9be25", [:mix], [{:msgpax, "~> 2.2.1", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4c20d3e601cad869705d9789f17a9242f245ce0bf2579fc835e96a6834663e2"}, - "spandex_ecto": {:hex, :spandex_ecto, "0.6.2", "845e0e0a115e84c218015e8a13cca7adb38e8b0a1b45010a51451a8c9961c551", [:mix], [{:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm", "ddeb5c279ca850a38eee6decc8f91b3f4929c76f141b3329293793be54d0c1c7"}, - "spandex_phoenix": {:hex, :spandex_phoenix, "1.0.5", "6bb632d3b8157bbbdac97e03aca6dc3b2afd115b6d015fc8206b009685cd3b27", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm", "f7bcd063fed9eb140373603f8a7340b71e219e3f62824c2c7571036f47e2ac3c"}, + "spandex": {:hex, :spandex, "3.2.0", "f8cd40146ea988c87f3c14054150c9a47ba17e53cd4515c00e1f93c29c45404d", [:mix], [{:decorator, "~> 1.2", [hex: :decorator, repo: "hexpm", optional: true]}, {:optimal, "~> 0.3.3", [hex: :optimal, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d0a7d5aef4c5af9cf5467f2003e8a5d8d2bdae3823a6cc95d776b9a2251d4d03"}, + "spandex_datadog": {:hex, :spandex_datadog, "1.3.0", "cabe82980f55612a8befa6c12904b1a429bf17faf7271a94b9aae278af6362cf", [:mix], [{:msgpax, "~> 2.2.1 or ~> 2.3", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c826e4e29d1612e866b2c7bae2df3beeee84fc57351968c2772672afe59b789c"}, + "spandex_ecto": {:hex, :spandex_ecto, "0.7.0", "259ad2feb7c834e774ec623f99c0fbacca8d60a73be212f92b75e37f853c81be", [:mix], [{:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm", "c64784be79d95538013b7c60828830411c5c7aff1f4e8d66dfe564b3c83b500e"}, + "spandex_phoenix": {:hex, :spandex_phoenix, "1.1.0", "9cff829d05258dd49a227c56711b19b69a8fd5d4873d8e9a92a4f4097e7322ab", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "265fe05c1736485fbb75d66ef7576682ebf6428c391dd54d22217f612fd4ddad"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, - "tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"}, - "timex": {:hex, :timex, "3.7.1", "1157bd18abf2dcfd61ed666b1f73c356b2e97197deccfecafad188629282fb61", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "cb5d815666e30625d517360523ddd023eb1b4bc27c564e7501c3d050115df523"}, + "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, + "timex": {:hex, :timex, "3.7.9", "790cdfc4acfce434e442f98c02ea6d84d0239073bfd668968f82ac63e9a6788d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "64691582e5bb87130f721fc709acfb70f24405833998fabf35be968984860ce1"}, "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, + "ueberauth": {:hex, :ueberauth, "0.10.3", "4a3bd7ab7b5d93d301d264f0f6858392654ee92171f4437d067d1ae227c051d9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "1394f36a6c64e97f2038cf95228e7e52b4cb75417962e30418fbe9902b30e6d3"}, + "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "wallaby": {:hex, :wallaby, "0.29.1", "7ad61c98a1425db291a392e45504e897b1271c746b40fe9f19f87a86729d2fac", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "21b1360c4b13adbb0a1ff5c1053204cc4489ac81e9579c6c5011a27915cb7415"}, + "wallaby": {:hex, :wallaby, "0.30.1", "81342a34080867ab359aca23de4d1d8c6bbdeb35d8ce2a8c42e42b758d539963", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "457251df6a94ff80816524136edbce6400cb1ee979586c90224ff634e9543d78"}, "web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"}, - "websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm", "b864fa076f059b615da4ab99240e515b26132ce4d2d0f9df5d7f22f01fa04b65"}, + "websocket_client": {:hex, :websocket_client, "1.5.0", "e825f23c51a867681a222148ed5200cc4a12e4fb5ff0b0b35963e916e2b5766b", [:rebar3], [], "hexpm", "2b9b201cc5c82b9d4e6966ad8e605832eab8f4ddb39f57ac62f34cb208b68de9"}, "wobserver": {:git, "https://github.com/poanetwork/wobserver.git", "13bcda30a87f4f0be1878920a79433ad831eefbe", [branch: "support-https"]}, }