commit 07a8bc3f2b77373d2a8a6b248ff9b171026518eb Author: nico Date: Tue Mar 21 11:53:40 2023 -0500 init diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..d688fc7 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,627 @@ +version: 2 +jobs: + build: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3-node-browsers + environment: + MIX_ENV: test + # match POSTGRES_PASSWORD for postgres image below + PGPASSWORD: postgres + # match POSTGRES_USER for postgres image below + PGUSER: postgres + + working_directory: ~/app + + steps: + - run: sudo apt-get update; sudo apt-get -y install autoconf build-essential libgmp3-dev libtool + + - checkout + - run: + command: ./bin/install_chrome_headless.sh + no_output_timeout: 2400 + + - run: mix local.hex --force + - run: mix local.rebar --force + + - run: + name: "ELIXIR_VERSION.lock" + command: echo "${ELIXIR_VERSION}" > ELIXIR_VERSION.lock + - run: + name: "OTP_VERSION.lock" + command: echo "${OTP_VERSION}" > OTP_VERSION.lock + + - restore_cache: + keys: + - v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }} + - v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }} + - v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }} + + - run: mix deps.get + + - restore_cache: + keys: + - v8-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }} + - v8-npm-install-{{ .Branch }} + - v8-npm-install + + - run: + command: npm install + working_directory: "apps/explorer" + + - save_cache: + key: v3-npm-install-{{ .Branch }}-{{ checksum "apps/explorer/package-lock.json" }} + paths: "apps/explorer/node_modules" + - save_cache: + key: v3-npm-install-{{ .Branch }} + paths: "apps/explorer/node_modules" + - save_cache: + key: v3-npm-install + paths: "apps/explorer/node_modules" + + - run: + command: npm install + working_directory: "apps/block_scout_web/assets" + + - save_cache: + key: v8-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }} + paths: "apps/block_scout_web/assets/node_modules" + - save_cache: + key: v8-npm-install-{{ .Branch }} + paths: "apps/block_scout_web/assets/node_modules" + - save_cache: + key: v8-npm-install + paths: "apps/block_scout_web/assets/node_modules" + + - run: mix compile + + # Ensure NIF is compiled for libsecp256k1 + - run: + command: make + working_directory: "deps/libsecp256k1" + + # `deps` needs to be cached with `_build` because `_build` will symlink into `deps` + + - save_cache: + key: v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }} + paths: + - deps + - _build + - save_cache: + key: v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }} + paths: + - deps + - _build + - save_cache: + key: v8-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }} + paths: + - deps + - _build + + - run: + name: Build assets + command: node node_modules/webpack/bin/webpack.js --mode development + working_directory: "apps/block_scout_web/assets" + + - persist_to_workspace: + root: . + paths: + - .circleci + - .credo.exs + - .dialyzer-ignore + - .formatter.exs + - .git + - .gitignore + - ELIXIR_VERSION.lock + - Gemfile + - Gemfile.lock + - OTP_VERSION.lock + - _build + - apps + - bin + - config + - deps + - doc + - mix.exs + - mix.lock + - appspec.yml + - rel + check_formatted: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3 + environment: + MIX_ENV: test + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: mix format --check-formatted + credo: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3 + environment: + MIX_ENV: test + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: mix local.hex --force + + - run: mix credo + deploy_aws: + docker: + # Ensure .tool-versions matches + - image: circleci/python:2.7-stretch + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - add_ssh_keys: + fingerprints: + - "c4:fd:a8:f8:48:a8:09:e5:3e:be:30:62:4d:6f:6f:36" + + - run: + name: Deploy to AWS + command: bin/deploy + dialyzer: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3 + environment: + MIX_ENV: test + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: mix local.hex --force + + - restore_cache: + keys: + - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }} + - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }} + - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }} + + - run: + name: Unpack PLT cache + command: | + mkdir -p _build/test + cp plts/dialyxir*.plt _build/test/ || true + mkdir -p ~/.mix + cp plts/dialyxir*.plt ~/.mix/ || true + + - run: mix dialyzer --plt + + - run: + name: Pack PLT cache + command: | + mkdir -p plts + cp _build/test/dialyxir*.plt plts/ + cp ~/.mix/dialyxir*.plt plts/ + + - save_cache: + key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }} + paths: + - plts + - save_cache: + key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }} + paths: + - plts + - save_cache: + key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }} + paths: + - plts + + - run: mix dialyzer --halt-exit-status + eslint: + docker: + # Ensure .tool-versions matches + - image: circleci/node:12.18.2-browsers-legacy + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: + name: ESLint + command: ./node_modules/.bin/eslint --format=junit --output-file="test/eslint/junit.xml" js/** + working_directory: apps/block_scout_web/assets + + - store_test_results: + path: apps/block_scout_web/assets/test + gettext: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3 + environment: + MIX_ENV: test + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: mix local.hex --force + + - run: + name: Check for missed translations + command: | + mix gettext.extract --merge | tee stdout.txt + ! grep "Wrote " stdout.txt + working_directory: "apps/block_scout_web" + + - store_artifacts: + path: apps/block_scout_web/priv/gettext + jest: + docker: + # Ensure .tool-versions matches + - image: circleci/node:12.18.2-browsers-legacy + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: + name: Jest + command: ./node_modules/.bin/jest + working_directory: apps/block_scout_web/assets + release: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3 + environment: + MIX_ENV: prod + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: mix local.hex --force + - run: mix local.rebar --force + - run: MIX_ENV=prod mix release + - run: + name: Collecting artifacts + command: | + find -name 'blockscout.tar.gz' -exec sh -c 'mkdir -p ci_artifact && cp "$@" ci_artifact/ci_artifact_blockscout.tar.gz' _ {} + + when: always + + - store_artifacts: + name: Uploading CI artifacts + path: ci_artifact/ci_artifact_blockscout.tar.gz + destination: ci_artifact_blockscout.tar.gz + sobelow: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3 + environment: + MIX_ENV: test + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: mix local.hex --force + + - run: + name: Scan explorer for vulnerabilities + command: mix sobelow --config + working_directory: "apps/explorer" + + - run: + name: Scan block_scout_web for vulnerabilities + command: mix sobelow --config + working_directory: "apps/block_scout_web" + # test_geth_http_websocket: + # docker: + # # Ensure .tool-versions matches + # - image: circleci/elixir:1.10.3-node-browsers + # environment: + # MIX_ENV: test + # # match POSTGRES_PASSWORD for postgres image below + # PGPASSWORD: postgres + # # match POSTGRES_USER for postgres image below + # PGUSER: postgres + # ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.HTTPWebSocket" + # ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Geth" + # - image: circleci/postgres:10.10-alpine + # environment: + # # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + # POSTGRES_DB: explorer_test + # # match PGPASSWORD for elixir image above + # POSTGRES_PASSWORD: postgres + # # match PGUSER for elixir image above + # POSTGRES_USER: postgres + + # working_directory: ~/app + + # steps: + # - attach_workspace: + # at: . + + # - run: + # command: ./bin/install_chrome_headless.sh + # no_output_timeout: 2400 + + # - run: mix local.hex --force + # - run: mix local.rebar --force + + # - run: + # name: Wait for DB + # command: dockerize -wait tcp://localhost:5432 -timeout 1m + + # - run: + # name: mix test --exclude no_geth + # command: | + # # Don't submit coverage report for forks, but let the build succeed + # if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then + # mix coveralls.html --exclude no_geth --parallel --umbrella + # else + # mix coveralls.circle --exclude no_geth --parallel --umbrella || + # # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status + # (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval) + # fi + + # - store_artifacts: + # path: cover/excoveralls.html + # - store_test_results: + # path: _build/test/junit + # test_geth_mox: + # docker: + # # Ensure .tool-versions matches + # - image: circleci/elixir:1.10.3-node-browsers + # environment: + # MIX_ENV: test + # # match POSTGRES_PASSWORD for postgres image below + # PGPASSWORD: postgres + # # match POSTGRES_USER for postgres image below + # PGUSER: postgres + # ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.Mox" + # ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + # - image: circleci/postgres:10.10-alpine + # environment: + # # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + # POSTGRES_DB: explorer_test + # # match PGPASSWORD for elixir image above + # POSTGRES_PASSWORD: postgres + # # match PGUSER for elixir image above + # POSTGRES_USER: postgres + + # working_directory: ~/app + + # steps: + # - attach_workspace: + # at: . + + # - run: + # command: ./bin/install_chrome_headless.sh + # no_output_timeout: 2400 + + # - run: mix local.hex --force + # - run: mix local.rebar --force + + # - run: + # name: Wait for DB + # command: dockerize -wait tcp://localhost:5432 -timeout 1m + + # - run: + # name: mix test --exclude no_geth + # command: | + # # Don't submit coverage report for forks, but let the build succeed + # if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then + # mix coveralls.html --exclude no_geth --parallel --umbrella + # else + # mix coveralls.circle --exclude no_geth --parallel --umbrella || + # # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status + # (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval) + # fi + + # - store_artifacts: + # path: cover/excoveralls.html + # - store_test_results: + # path: _build/test/junit + # test_nethermind_http_websocket: + # docker: + # # Ensure .tool-versions matches + # - image: circleci/elixir:1.10.3-node-browsers + # environment: + # MIX_ENV: test + # # match POSTGRES_PASSWORD for postgres image below + # PGPASSWORD: postgres + # # match POSTGRES_USER for postgres image below + # PGUSER: postgres + # 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 + # POSTGRES_DB: explorer_test + # # match PGPASSWORD for elixir image above + # POSTGRES_PASSWORD: postgres + # # match PGUSER for elixir image above + # POSTGRES_USER: postgres + + # working_directory: ~/app + + # steps: + # - attach_workspace: + # at: . + + # - run: + # command: ./bin/install_chrome_headless.sh + # no_output_timeout: 2400 + + # - run: mix local.hex --force + # - run: mix local.rebar --force + + # - run: + # name: Wait for DB + # command: dockerize -wait tcp://localhost:5432 -timeout 1m + + # - run: + # 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_nethermind --parallel --umbrella + # else + # 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 + + # - store_artifacts: + # path: cover/excoveralls.html + # - store_test_results: + # path: _build/test/junit + test_nethermind_mox: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3-node-browsers + environment: + MIX_ENV: test + # match POSTGRES_PASSWORD for postgres image below + PGPASSWORD: postgres + # match POSTGRES_USER for postgres image below + PGUSER: postgres + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + - image: circleci/postgres:10.10-alpine + environment: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: + command: ./bin/install_chrome_headless.sh + no_output_timeout: 2400 + + - run: mix local.hex --force + - run: mix local.rebar --force + + - run: + name: Wait for DB + command: dockerize -wait tcp://localhost:5432 -timeout 1m + + - run: + 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_nethermind --parallel --umbrella + else + 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 + + - store_artifacts: + path: cover/excoveralls.html + - store_test_results: + path: _build/test/junit + coveralls_merge: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.10.3 + environment: + MIX_ENV: test + + steps: + - run: + name: Tell coveralls.io build is done + command: curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" +workflows: + version: 2 + primary: + jobs: + - build + - check_formatted: + requires: + - build + # This unfortunately will only fire if all the tests pass because of how `requires` works + - coveralls_merge: + requires: + # - test_nethermind_http_websocket + - test_nethermind_mox + # - test_geth_http_websocket + # - test_geth_mox + - credo: + requires: + - build + - deploy_aws: + filters: + branches: + only: + - production + - staging + - /deploy-[A-Za-z0-9]+$/ + requires: + - check_formatted + - credo + - eslint + - jest + - sobelow + # - test_nethermind_http_websocket + - test_nethermind_mox + # - test_geth_http_websocket + # - test_geth_mox + - dialyzer: + requires: + - build + - eslint: + requires: + - build + - gettext: + requires: + - build + - jest: + requires: + - build + - release: + requires: + - build + - sobelow: + requires: + - build + # - test_nethermind_http_websocket: + # requires: + # - build + - test_nethermind_mox: + requires: + - build + # - test_geth_http_websocket: + # requires: + # - build + # - test_geth_mox: + # requires: + # - build diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..ba4d7fe --- /dev/null +++ b/.credo.exs @@ -0,0 +1,150 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any exec using `mix credo -C `. If no exec name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: ["lib/", "src/", "web/", "apps/*/lib/**/*.{ex,exs}"], + excluded: [ + ~r"/_build/", + ~r"/deps/", + ~r"/node_modules/", + ~r"/apps/block_scout_web/lib/block_scout_web.ex" + ] + }, + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: true, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: [ + # outdated by formatter in Elixir 1.6. See https://github.com/rrrene/credo/issues/505 + {Credo.Check.Consistency.LineEndings, false}, + {Credo.Check.Consistency.SpaceAroundOperators, false}, + {Credo.Check.Consistency.SpaceInParentheses, false}, + {Credo.Check.Consistency.TabsOrSpaces, false}, + {Credo.Check.Readability.LargeNumbers, false}, + {Credo.Check.Readability.MaxLineLength, false}, + {Credo.Check.Readability.ParenthesesInCondition, false}, + {Credo.Check.Readability.RedundantBlankLines, false}, + {Credo.Check.Readability.Semicolons, false}, + {Credo.Check.Readability.SpaceAfterCommas, false}, + {Credo.Check.Readability.TrailingBlankLine, false}, + {Credo.Check.Readability.TrailingWhiteSpace, false}, + + # outdated by lazy Logger in Elixir 1.7. See https://elixir-lang.org/blog/2018/07/25/elixir-v1-7-0-released/ + {Credo.Check.Warning.LazyLogging, false}, + + # not handled by formatter + {Credo.Check.Consistency.ExceptionNames}, + {Credo.Check.Consistency.ParameterPatternMatching}, + + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + excluded_namespaces: ~w(Block Blocks Import Runner Socket SpandexDatadog Task), + excluded_lastnames: + ~w(Address DateTime Exporter Fetcher Full Instrumenter Logger Monitor Name Number Repo Spec Time Unit), + priority: :low}, + + # For some checks, you can also set other parameters + # + # If you don't want the `setup` and `test` macro calls in ExUnit tests + # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just + # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. + # + {Credo.Check.Design.DuplicatedCode, excluded_macros: [], mass_threshold: 80}, + + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, exit_status: 0}, + {Credo.Check.Design.TagFIXME}, + {Credo.Check.Readability.FunctionNames}, + {Credo.Check.Readability.ModuleAttributeNames}, + {Credo.Check.Readability.ModuleDoc}, + {Credo.Check.Readability.ModuleNames}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs}, + {Credo.Check.Readability.PredicateFunctionNames}, + {Credo.Check.Readability.PreferImplicitTry}, + {Credo.Check.Readability.StringSigils}, + {Credo.Check.Readability.VariableNames}, + {Credo.Check.Refactor.DoubleBooleanNegation}, + {Credo.Check.Refactor.CondStatements}, + {Credo.Check.Refactor.CyclomaticComplexity}, + {Credo.Check.Refactor.FunctionArity}, + {Credo.Check.Refactor.LongQuoteBlocks}, + {Credo.Check.Refactor.MatchInCondition}, + {Credo.Check.Refactor.NegatedConditionsInUnless}, + {Credo.Check.Refactor.NegatedConditionsWithElse}, + {Credo.Check.Refactor.Nesting}, + {Credo.Check.Refactor.PipeChainStart}, + {Credo.Check.Refactor.UnlessWithElse}, + {Credo.Check.Warning.BoolOperationOnSameValues}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck}, + {Credo.Check.Warning.IExPry}, + {Credo.Check.Warning.IoInspect}, + {Credo.Check.Warning.OperationOnSameValues}, + {Credo.Check.Warning.OperationWithConstantResult}, + {Credo.Check.Warning.UnusedEnumOperation}, + {Credo.Check.Warning.UnusedFileOperation}, + {Credo.Check.Warning.UnusedKeywordOperation}, + {Credo.Check.Warning.UnusedListOperation}, + {Credo.Check.Warning.UnusedPathOperation}, + {Credo.Check.Warning.UnusedRegexOperation}, + {Credo.Check.Warning.UnusedStringOperation}, + {Credo.Check.Warning.UnusedTupleOperation}, + {Credo.Check.Warning.RaiseInsideRescue, false}, + + # Controversial and experimental checks (opt-in, just remove `, false`) + # + # TODO reenable before merging optimized-indexer branch + {Credo.Check.Refactor.ABCSize, false}, + {Credo.Check.Refactor.AppendSingleItem}, + {Credo.Check.Refactor.VariableRebinding}, + {Credo.Check.Warning.MapGetUnsafePass}, + {Credo.Check.Consistency.MultiAliasImportRequireUse} + + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + ] +} diff --git a/.dialyzer-ignore b/.dialyzer-ignore new file mode 100644 index 0000000..654d998 --- /dev/null +++ b/.dialyzer-ignore @@ -0,0 +1,35 @@ +:0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 +:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 +:0: Unknown type 'Elixir.Map':t/0 +:0: Unknown type 'Elixir.Hash':t/0 +:0: Unknown type 'Elixir.Address':t/0 +apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return +lib/ethereum_jsonrpc/rolling_window.ex:173 +lib/explorer/repo/prometheus_logger.ex:8 +lib/explorer/smart_contract/solidity/publisher_worker.ex:1 +lib/explorer/smart_contract/vyper/publisher_worker.ex:1 +lib/explorer/smart_contract/solidity/publisher_worker.ex:6 +lib/explorer/smart_contract/vyper/publisher_worker.ex:6 +apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return +apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() +lib/block_scout_web/router.ex:1 +lib/block_scout_web/schema/types.ex:31 +lib/phoenix/router.ex:324 +lib/phoenix/router.ex:402 +lib/block_scout_web/views/layout_view.ex:145: The call 'Elixir.Poison.Parser':'parse!' +lib/block_scout_web/views/layout_view.ex:237: The call 'Elixir.Poison.Parser':'parse!' +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:310 +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: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 +lib/indexer/buffered_task.ex:402 +lib/indexer/buffered_task.ex:451 +lib/indexer/memory/monitor.ex:160 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b52edd4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +_build +deps +apps/block_scout_web/assets/node_modules +apps/explorer/node_modules +test +.git +.circleci +logs +apps/*/test diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..69ae0d2 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,11 @@ +[ + inputs: [ + ".credo.exs", + ".formatter.exs", + "apps/*/mix.exs", + "apps/*/{benchmarks,config,lib,priv,test}/**/*.{ex,exs}", + "mix.exs", + "{config}/**/*.{ex,exs}" + ], + line_length: 120 +] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b6a9ec4 --- /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 0000000..9b8a5cf --- /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 new file mode 100644 index 0000000..738095b --- /dev/null +++ b/.github/workflows/config.yml @@ -0,0 +1,584 @@ +name: Blockscout + +on: + push: + branches: + - master + pull_request: + branches: + - master + +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-latest + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + + - name: "ELIXIR_VERSION.lock" + run: echo "${ELIXIR_VERSION}" > ELIXIR_VERSION.lock + + - name: "OTP_VERSION.lock" + run: echo "${OTP_VERSION}" > OTP_VERSION.lock + + - 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- + + - name: Conditionally build Mix deps cache + if: steps.deps-cache.outputs.cache-hit != 'true' + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + mix deps.compile + cd deps/libsecp256k1 + make + + - name: Restore Explorer NPM Cache + uses: actions/cache@v2 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- + + - name: Conditionally build Explorer NPM Cache + if: steps.explorer-npm-cache.outputs.cache-hit != 'true' + run: npm install + working-directory: apps/explorer + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v2 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Conditionally build Blockscout Web NPM Cache + if: steps.blockscoutweb-npm-cache.outputs.cache-hit != 'true' + run: npm install + working-directory: apps/block_scout_web/assets + + credo: + name: Credo + 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 credo + + check_formatted: + name: Code formatting checks + 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 format --check-formatted + dialyzer: + name: Dialyzer static analysis + 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-" + + - name: Restore Dialyzer Cache + uses: actions/cache@v2 + id: dialyzer-cache + with: + path: priv/plts + 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-" + + - name: Conditionally build Dialyzer Cache + if: steps.dialyzer-cache.output.cache-hit != 'true' + run: | + mkdir -p priv/plts + mix dialyzer --plt + + - 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-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: 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-" + + - name: Scan explorer for vulnerabilities + run: mix sobelow --config + working-directory: "apps/explorer" + - name: Scan block_scout_web for vulnerabilities + run: mix sobelow --config + working-directory: "apps/block_scout_web" + eslint: + name: ESLint + 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: 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-" + + - name: Restore Explorer NPM Cache + uses: actions/cache@v2 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v2 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Build assets + run: node node_modules/webpack/bin/webpack.js --mode development + working-directory: "apps/block_scout_web/assets" + + - run: ./node_modules/.bin/eslint --format=junit --output-file="test/eslint/junit.xml" js/** + working-directory: apps/block_scout_web/assets + jest: + name: JS Tests + 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: 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-" + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v2 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Build assets + run: node node_modules/webpack/bin/webpack.js --mode development + working-directory: "apps/block_scout_web/assets" + + - run: ./node_modules/.bin/jest + working-directory: apps/block_scout_web/assets + + test_nethermind_mox_ethereum_jsonrpc: + name: EthereumJSONRPC Tests + runs-on: ubuntu-latest + needs: build-and-cache + services: + postgres: + image: postgres + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV + + - name: 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: ./bin/install_chrome_headless.sh + - name: mix test --exclude no_nethermind + run: | + cd apps/ethereum_jsonrpc + mix compile + 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.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + test_nethermind_mox_explorer: + name: Explorer Tests + runs-on: ubuntu-latest + needs: build-and-cache + services: + postgres: + image: postgres + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV + + - name: 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-" + + - name: Restore Explorer NPM Cache + uses: actions/cache@v2 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm + + - run: ./bin/install_chrome_headless.sh + - 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_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.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + test_nethermind_mox_indexer: + name: Indexer Tests + runs-on: ubuntu-latest + needs: build-and-cache + services: + postgres: + image: postgres + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV + + - name: 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: ./bin/install_chrome_headless.sh + + - 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_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.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + + test_nethermind_mox_block_scout_web: + name: Blockscout Web Tests + runs-on: ubuntu-latest + needs: build-and-cache + services: + redis_db: + image: 'redis:alpine' + ports: + - 6379:6379 + + postgres: + image: postgres + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV + + - name: 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-" + + + - name: Restore Explorer NPM Cache + uses: actions/cache@v2 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v2 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Build assets + run: node node_modules/webpack/bin/webpack.js --mode development + working-directory: "apps/block_scout_web/assets" + + - run: ./bin/install_chrome_headless.sh + + - 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_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.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + CHAIN_ID: "77" + ADMIN_PANEL_ENABLED: "true" + ACCOUNT_ENABLED: "true" + ACCOUNT_REDIS_URL: "redis://localhost:6379" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..987f911 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,86 @@ +name: Run E2E tests k8s + +on: + # push: + pull_request: + workflow_dispatch: + +env: + K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }} + K8S_HOST: ${{ secrets.K8S_HOST }} + BASTION_HOST: ${{ secrets.BASTION_HOST }} + K8S_PORT: ${{ secrets.K8S_PORT }} + USERNAME: ${{ secrets.USERNAME }} + BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}} + +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=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:pr-buildcache + cache-to: type=registry,ref=blockscout/blockscout:pr-buildcache,mode=max + tags: blockscout/blockscout:pr-${{ 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= + + deploy_and_tests: + needs: push_to_registry + uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master + with: + blockscoutImage: blockscout/blockscout:pr-${{ 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-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml new file mode 100644 index 0000000..ec503dd --- /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 0000000..0586187 --- /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 new file mode 100644 index 0000000..0bc441a --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# App artifacts +/_build +/apps/*/cover +/apps/*/logs +/cover +/db +/deps +/doc +/*.ez +/logs + +# mix dialyzer artifacts +/priv/plts + +# Generated on crash by the VM +erl_crash.dump + +# Generated on crash by NPM +npm-debug.log + +# Static artifacts +/apps/**/node_modules + +# Since we are building assets from assets/, +# we ignore priv/static. You may want to comment +# this depending on your deployment strategy. +/apps/*/priv/static/ + +# Files matching config/*.secret.exs pattern contain sensitive +# data and you should not commit them into version control. +# +# Alternatively, you may comment the line below and commit the +# secrets files as long as you replace their contents by environment +# variables. +/apps/*/config/*.secret.exs + +# Wallaby screenshots +screenshots/ + +# Sobelow +.sobelow + +# osx +.DS_Store + +# mix phx.gen.cert self-signed certs for dev +/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/.pairs b/.pairs new file mode 100644 index 0000000..e40bd6e --- /dev/null +++ b/.pairs @@ -0,0 +1,13 @@ +pairs: + cj: CJ Bryan; cj + dr: Doc Ritezel; doc + mo: Matt Olenick; matto + db: Derek Barnes; dgb + rdwb: Desmond Bowe; des + +email: + prefix: pair + domain: ministryofvelocity.com + no_solo_prefix: true + +global: true diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..7b8898c --- /dev/null +++ b/.tool-versions @@ -0,0 +1,3 @@ +elixir 1.14.1 +erlang 25.1.1 +nodejs 16.16.0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..383e5d7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2046 @@ +## Current + +### Features + +- [#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) - 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 + +### Fixes + +- [#6390](https://github.com/blockscout/blockscout/pull/6390) - Fix transactions responses in API v2 +- [#6357](https://github.com/blockscout/blockscout/pull/6357), [#6409](https://github.com/blockscout/blockscout/pull/6409) - 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 + +### Chore + +- [#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 + + +## 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 +- [#5612](https://github.com/blockscout/blockscout/pull/5612) - Fix token transfers order +- [#5626](https://github.com/blockscout/blockscout/pull/5626) - Fix vyper compiler versions order +- [#5603](https://github.com/blockscout/blockscout/pull/5603) - Fix failing verification attempts +- [#5598](https://github.com/blockscout/blockscout/pull/5598) - Fix token dropdown +- [#5592](https://github.com/blockscout/blockscout/pull/5592) - Burn fees for legacy transactions +- [#5568](https://github.com/blockscout/blockscout/pull/5568) - Add regexp for ipfs checking +- [#5567](https://github.com/blockscout/blockscout/pull/5567) - Sanitize token name and symbol before insert into DB, display in the application +- [#5564](https://github.com/blockscout/blockscout/pull/5564) - Add fallback clauses to `string_to_..._hash` functions +- [#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 +- [#5640](https://github.com/blockscout/blockscout/pull/5640) - Clean up and fix tests, reduce amount of warnings +- [#5625](https://github.com/blockscout/blockscout/pull/5625) - Get rid of some redirects to checksummed address url +- [#5623](https://github.com/blockscout/blockscout/pull/5623) - Allow hyphen in DB password +- [#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 +- [#5312](https://github.com/blockscout/blockscout/pull/5312) - Add OpenZeppelin proxy storage slot +- [#5302](https://github.com/blockscout/blockscout/pull/5302) - Add specific tx receipt fields for the GoQuorum client +- [#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 +- [#5508](https://github.com/blockscout/blockscout/pull/5508) - Hide indexing banner if we fetched internal transactions from TRACE_FIRST_BLOCK +- [#5504](https://github.com/blockscout/blockscout/pull/5504) - Extend TRACE_FIRST_BLOCK env var to geth variant +- [#5488](https://github.com/blockscout/blockscout/pull/5488) - Split long contract output to multiple lines +- [#5487](https://github.com/blockscout/blockscout/pull/5487) - Fix array displaying in decoded constructor args +- [#5482](https://github.com/blockscout/blockscout/pull/5482) - Fix for querying of the contract read functions +- [#5455](https://github.com/blockscout/blockscout/pull/5455) - Fix unverified_smart_contract function: add md5 of bytecode to the changeset +- [#5454](https://github.com/blockscout/blockscout/pull/5454) - Docker: Fix the qemu-x86_64 signal 11 error on Apple Silicon +- [#5443](https://github.com/blockscout/blockscout/pull/5443) - Geth: display tx revert reason +- [#5420](https://github.com/blockscout/blockscout/pull/5420) - Deduplicate addresses and coin balances before inserting to the DB +- [#5416](https://github.com/blockscout/blockscout/pull/5416) - Fix getsourcecode for EOA addresses +- [#5413](https://github.com/blockscout/blockscout/pull/5413) - Fix params encoding for read contracts methods +- [#5411](https://github.com/blockscout/blockscout/pull/5411) - Fix character_not_in_repertoire error for tx revert reason +- [#5410](https://github.com/blockscout/blockscout/pull/5410) - Handle exited realtime fetcher +- [#5383](https://github.com/blockscout/blockscout/pull/5383) - Fix reload transactions button +- [#5381](https://github.com/blockscout/blockscout/pull/5381), [#5397](https://github.com/blockscout/blockscout/pull/5397) - Fix exchange rate broadcast error +- [#5375](https://github.com/blockscout/blockscout/pull/5375) - Fix pending transactions fetcher +- [#5374](https://github.com/blockscout/blockscout/pull/5374) - Return all ERC-1155's token instances in tokenList api endpoint +- [#5342](https://github.com/blockscout/blockscout/pull/5342) - Fix 500 error on NF token page with nil metadata +- [#5319](https://github.com/blockscout/blockscout/pull/5319), [#5357](https://github.com/blockscout/blockscout/pull/5357), [#5425](https://github.com/blockscout/blockscout/pull/5425) - Empty blocks sanitizer performance improvement +- [#5310](https://github.com/blockscout/blockscout/pull/5310) - Fix flash on reload in dark mode +- [#5306](https://github.com/blockscout/blockscout/pull/5306) - Fix indexer bug +- [#5300](https://github.com/blockscout/blockscout/pull/5300), [#5305](https://github.com/blockscout/blockscout/pull/5305) - Token instance page: general video improvements +- [#5136](https://github.com/blockscout/blockscout/pull/5136) - Improve contract verification +- [#5285](https://github.com/blockscout/blockscout/pull/5285) - Fix verified smart-contract bytecode twins feature +- [#5269](https://github.com/blockscout/blockscout/pull/5269) - Address Page: Fix implementation address align +- [#5264](https://github.com/blockscout/blockscout/pull/5264) - Fix bug with 500 response on `partial` sourcify status +- [#5263](https://github.com/blockscout/blockscout/pull/5263) - Fix bug with name absence for contract +- [#5259](https://github.com/blockscout/blockscout/pull/5259) - Fix `coin-balances/by-day` bug +- [#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 +- [#5458](https://github.com/blockscout/blockscout/pull/5458) - Decrease min safe polling period for realtime fetcher +- [#5456](https://github.com/blockscout/blockscout/pull/5456) - Ignore arbitrary block details fields for custom Ethereum clients +- [#5450](https://github.com/blockscout/blockscout/pull/5450) - Logging error in publishing of smart-contract +- [#5433](https://github.com/blockscout/blockscout/pull/5433) - Caching modules refactoring +- [#5419](https://github.com/blockscout/blockscout/pull/5419) - Add check if address exists for some api methods +- [#5408](https://github.com/blockscout/blockscout/pull/5408) - Update websocket_client hex package +- [#5407](https://github.com/blockscout/blockscout/pull/5407) - Update hackney, certifi, tzdata +- [#5369](https://github.com/blockscout/blockscout/pull/5369) - Manage indexer memory limit +- [#5368](https://github.com/blockscout/blockscout/pull/5368) - Refactoring from SourcifyFilePathBackfiller +- [#5367](https://github.com/blockscout/blockscout/pull/5367) - Resolve Prototype Pollution in minimist dependency +- [#5366](https://github.com/blockscout/blockscout/pull/5366) - Fix Vyper smart-contract verification form tooltips +- [#5348](https://github.com/blockscout/blockscout/pull/5348) - Block data for Avalanche: pass blockExtraData param +- [#5341](https://github.com/blockscout/blockscout/pull/5341) - Remove unused broadcasts +- [#5318](https://github.com/blockscout/blockscout/pull/5318) - Eliminate Jquery import from chart-loader.js +- [#5317](https://github.com/blockscout/blockscout/pull/5317) - NPM audit +- [#5303](https://github.com/blockscout/blockscout/pull/5303) - Besu: revertReason support in trace +- [#5301](https://github.com/blockscout/blockscout/pull/5301) - Allow specific block keys for sgb/ava +- [#5295](https://github.com/blockscout/blockscout/pull/5295) - CI pipeline: build and push Docker image to Docker Hub on every release +- [#5290](https://github.com/blockscout/blockscout/pull/5290) - Bump ex_doc from 0.25.2 to 0.28.2 +- [#5289](https://github.com/blockscout/blockscout/pull/5289) - Bump ex_abi from 1.5.9 to 1.5.11 +- [#5288](https://github.com/blockscout/blockscout/pull/5288) - Makefile: find exact container by name +- [#5287](https://github.com/blockscout/blockscout/pull/5287) - Docker: modify native token symbol +- [#5286](https://github.com/blockscout/blockscout/pull/5286) - Change namespace for one of the SmartContractViewTest test +- [#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 +- [#5105](https://github.com/blockscout/blockscout/pull/5105) - Redesign token page +- [#5016](https://github.com/blockscout/blockscout/pull/5016) - Add view for internal transactions error +- [#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` +- [#5212](https://github.com/blockscout/blockscout/pull/5212) - Fix `gas_used` value bug +- [#5197](https://github.com/blockscout/blockscout/pull/5197) - Fix contract functions outputs +- [#5196](https://github.com/blockscout/blockscout/pull/5196) - Various Docker setup fixes +- [#5192](https://github.com/blockscout/blockscout/pull/5192) - Fix DATABASE_URL config parser +- [#5191](https://github.com/blockscout/blockscout/pull/5191) - Add empty view for new addresses +- [#5184](https://github.com/blockscout/blockscout/pull/5184) - eth_call method: remove from param from the request, if it is null +- [#5172](https://github.com/blockscout/blockscout/pull/5172), [#5182](https://github.com/blockscout/blockscout/pull/5182) - Reduced the size of js bundles +- [#5169](https://github.com/blockscout/blockscout/pull/5169) - Fix several UI bugs; Add tooltip to the prev/next block buttons +- [#5166](https://github.com/blockscout/blockscout/pull/5166), [#5198](https://github.com/blockscout/blockscout/pull/5198) - Fix contracts verification bugs +- [#5160](https://github.com/blockscout/blockscout/pull/5160) - Fix blocks validated hint +- [#5155](https://github.com/blockscout/blockscout/pull/5155) - Fix get_implementation_abi_from_proxy/2 implementation +- [#5154](https://github.com/blockscout/blockscout/pull/5154) - Fix token counters bug +- [#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 +- [#5224](https://github.com/blockscout/blockscout/pull/5224) - Webpack config refactoring +- [#5223](https://github.com/blockscout/blockscout/pull/5223) - Migrate fontawesome 5 -> 6 +- [#5202](https://github.com/blockscout/blockscout/pull/5202), [#5229](https://github.com/blockscout/blockscout/pull/5229) - Docker setup Makefile release/publish tasks +- [#5195](https://github.com/blockscout/blockscout/pull/5195) - Add Berlin, London to the list of default EVM versions +- [#5190](https://github.com/blockscout/blockscout/pull/5190) - Set 8545 as default port everywhere except Ganache JSON RPC variant +- [#5189](https://github.com/blockscout/blockscout/pull/5189) - ENV var to manage pending transactions fetcher switching off +- [#5171](https://github.com/blockscout/blockscout/pull/5171) - Replace lodash NPM package with tiny lodash modules +- [#5170](https://github.com/blockscout/blockscout/pull/5170) - Token price row name fix +- [#5153](https://github.com/blockscout/blockscout/pull/5153) - Discord link instead of Gitter +- [#5142](https://github.com/blockscout/blockscout/pull/5142) - Updated some outdated npm packages +- [#5140](https://github.com/blockscout/blockscout/pull/5140) - Babel minor and core-js major updates +- [#5139](https://github.com/blockscout/blockscout/pull/5139) - Eslint major update +- [#5138](https://github.com/blockscout/blockscout/pull/5138) - Webpack minor update +- [#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 +- [#5066](https://github.com/blockscout/blockscout/pull/5066) - Fix read contract page bug +- [#5034](https://github.com/blockscout/blockscout/pull/5034) - Fix broken functions input at transaction page +- [#5025](https://github.com/blockscout/blockscout/pull/5025) - Add standard input JSON files validation +- [#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 +- [#5004](https://github.com/blockscout/blockscout/pull/5004) - Add ability to set up a separate DB endpoint for the API endpoints +- [#4989](https://github.com/blockscout/blockscout/pull/4989), [#4991](https://github.com/blockscout/blockscout/pull/4991) - Bridged tokens list API endpoint +- [#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 +- [#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 +- [#5008](https://github.com/blockscout/blockscout/pull/5008) - Extend decimals cap in format_according_to_decimals up to 24 +- [#5005](https://github.com/blockscout/blockscout/pull/5005) - Fix falsy appearance `Connection Lost` warning on reload/switch page +- [#5003](https://github.com/blockscout/blockscout/pull/5003) - API router refactoring +- [#4992](https://github.com/blockscout/blockscout/pull/4992) - Fix `type` field in transactions after enabling 1559 +- [#4979](https://github.com/blockscout/blockscout/pull/4979), [#4993](https://github.com/blockscout/blockscout/pull/4993) - Store total gas_used in addresses table +- [#4977](https://github.com/blockscout/blockscout/pull/4977) - Export token transfers on address: include transfers on contract itself +- [#4976](https://github.com/blockscout/blockscout/pull/4976) - Handle :econnrefused in pending transactions fetcher +- [#4965](https://github.com/blockscout/blockscout/pull/4965) - Fix search field appearance on medium size screens +- [#4945](https://github.com/blockscout/blockscout/pull/4945) - Fix `Verify & Publish` button link +- [#4938](https://github.com/blockscout/blockscout/pull/4938) - Fix displaying of nested arrays for contracts read +- [#4888](https://github.com/blockscout/blockscout/pull/4888) - Fix fetch_top_tokens method: add nulls last for token holders desc order +- [#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 +- [#5014](https://github.com/blockscout/blockscout/pull/5014) - Separate FIRST_BLOCK and TRACE_FIRST_BLOCK option for blocks import and tracing methods +- [#4998](https://github.com/blockscout/blockscout/pull/4998) - API endpoints logger +- [#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 +- [#4761](https://github.com/blockscout/blockscout/pull/4761) - ERC-1155 support +- [#4739](https://github.com/blockscout/blockscout/pull/4739) - Improve logs and inputs decoding +- [#4747](https://github.com/blockscout/blockscout/pull/4747) - Advanced CSV export +- [#4745](https://github.com/blockscout/blockscout/pull/4745) - Vyper contracts verification +- [#4699](https://github.com/blockscout/blockscout/pull/4699), [#4793](https://github.com/blockscout/blockscout/pull/4793), [#4820](https://github.com/blockscout/blockscout/pull/4820), [#4827](https://github.com/blockscout/blockscout/pull/4827) - Address page facelifting +- [#4667](https://github.com/blockscout/blockscout/pull/4667) - Transaction Page: Add expand/collapse button for long contract method data +- [#4641](https://github.com/blockscout/blockscout/pull/4641), [#4733](https://github.com/blockscout/blockscout/pull/4733) - Improve Read Contract page logic +- [#4660](https://github.com/blockscout/blockscout/pull/4660) - Save Sourcify path instead of filename +- [#4656](https://github.com/blockscout/blockscout/pull/4656) - Open in Tenderly button +- [#4655](https://github.com/blockscout/blockscout/pull/4655), [#4676](https://github.com/blockscout/blockscout/pull/4676) - EIP-3091 support +- [#4621](https://github.com/blockscout/blockscout/pull/4621) - Add beacon contract address slot for proxy +- [#4625](https://github.com/blockscout/blockscout/pull/4625) - Contract address page: Add implementation link to the overview of proxy contracts +- [#4624](https://github.com/blockscout/blockscout/pull/4624) - Support HTML tags in alert message +- [#4608](https://github.com/blockscout/blockscout/pull/4608), [#4622](https://github.com/blockscout/blockscout/pull/4622) - Block Details page: Improved style of transactions button +- [#4596](https://github.com/blockscout/blockscout/pull/4596), [#4681](https://github.com/blockscout/blockscout/pull/4681), [#4693](https://github.com/blockscout/blockscout/pull/4693) - Display token icon for bridged with Mainnet tokens or identicons for other tokens +- [#4520](https://github.com/blockscout/blockscout/pull/4520) - Add support for EIP-1559 +- [#4593](https://github.com/blockscout/blockscout/pull/4593) - Add status in `Position` pane for txs have no block +- [#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 +- [#4840](https://github.com/blockscout/blockscout/pull/4840) - Replace Enum.dedup with Enum.uniq where actually uniq items are expected +- [#4835](https://github.com/blockscout/blockscout/pull/4835) - Fix view for broken token icons +- [#4830](https://github.com/blockscout/blockscout/pull/4830) - Speed up txs per day chart data collection +- [#4818](https://github.com/blockscout/blockscout/pull/4818) - Fix for extract_omni_bridged_token_metadata_wrapper method +- [#4812](https://github.com/blockscout/blockscout/pull/4812), [#4815](https://github.com/blockscout/blockscout/pull/4815) - Check if exists custom_cap property of extended token object before access it +- [#4810](https://github.com/blockscout/blockscout/pull/4810) - Show `nil` block.size as `N/A bytes` +- [#4806](https://github.com/blockscout/blockscout/pull/4806) - Get token type for token balance update if it is empty +- [#4802](https://github.com/blockscout/blockscout/pull/4802) - Fix floating tooltip on the main page +- [#4801](https://github.com/blockscout/blockscout/pull/4801) - Added clauses and tests for get_total_staked_and_ordered/1 +- [#4798](https://github.com/blockscout/blockscout/pull/4798) - Token instance View contract icon Safari fix +- [#4796](https://github.com/blockscout/blockscout/pull/4796) - Fix nil.timestamp issue +- [#4764](https://github.com/blockscout/blockscout/pull/4764) - Add cleaning of substrings of `require` messages from parsed constructor arguments +- [#4778](https://github.com/blockscout/blockscout/pull/4778) - Migrate :optimization_runs field type: `int4 -> int8` in `smart_contracts` table +- [#4768](https://github.com/blockscout/blockscout/pull/4768) - Block Details page: handle zero division +- [#4751](https://github.com/blockscout/blockscout/pull/4751) - Change text and link for `trade STAKE` button +- [#4746](https://github.com/blockscout/blockscout/pull/4746) - Fix comparison of decimal value +- [#4711](https://github.com/blockscout/blockscout/pull/4711) - Add trimming to the contract functions inputs +- [#4729](https://github.com/blockscout/blockscout/pull/4729) - Fix bugs with fees in cases of txs with `gas price = 0` +- [#4725](https://github.com/blockscout/blockscout/pull/4725) - Fix hardcoded coin name on transaction's and block's page +- [#4724](https://github.com/blockscout/blockscout/pull/4724), [#4842](https://github.com/blockscout/blockscout/pull/4841) - Sanitizer of "empty" blocks +- [#4717](https://github.com/blockscout/blockscout/pull/4717) - Contract verification fix: check only success creation tx +- [#4713](https://github.com/blockscout/blockscout/pull/4713) - Search input field: sanitize input +- [#4703](https://github.com/blockscout/blockscout/pull/4703) - Block Details page: Fix pagination on the Transactions tab +- [#4686](https://github.com/blockscout/blockscout/pull/4686) - Block page: check gas limit value before division +- [#4678](https://github.com/blockscout/blockscout/pull/4678) - Internal transactions indexer: fix issue of some pending transactions never become confirmed +- [#4668](https://github.com/blockscout/blockscout/pull/4668) - Fix css for dark theme +- [#4654](https://github.com/blockscout/blockscout/pull/4654) - AddressView: Change `@burn_address` to string `0x0000000000000000000000000000000000000000` +- [#4626](https://github.com/blockscout/blockscout/pull/4626) - Refine view of popup for reverted tx +- [#4640](https://github.com/blockscout/blockscout/pull/4640) - Token page: fixes in mobile view +- [#4612](https://github.com/blockscout/blockscout/pull/4612) - Hide error selector in the contract's functions list +- [#4615](https://github.com/blockscout/blockscout/pull/4615) - Fix broken style for `View more transfers` button +- [#4592](https://github.com/blockscout/blockscout/pull/4592) - Add `type` field for `receive` and `fallback` entities of a Smart Contract +- [#4601](https://github.com/blockscout/blockscout/pull/4601) - Fix endless Fetching tokens... message on empty addresses +- [#4591](https://github.com/blockscout/blockscout/pull/4591) - Add step and min value for txValue input field +- [#4589](https://github.com/blockscout/blockscout/pull/4589) - Fix solid outputs on contract read page +- [#4586](https://github.com/blockscout/blockscout/pull/4586) - Fix floating tooltips on the token transfer family blocks +- [#4587](https://github.com/blockscout/blockscout/pull/4587) - Enable navbar menu on Search results page +- [#4582](https://github.com/blockscout/blockscout/pull/4582) - Fix NaN input on write contract page + +### Chore + +- [#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 +- [#4860](https://github.com/blockscout/blockscout/pull/4860) - Node 16 support +- [#4828](https://github.com/blockscout/blockscout/pull/4828) - Logging for txs/day chart +- [#4823](https://github.com/blockscout/blockscout/pull/4823) - Various error handlers with unresponsive JSON RPC endpoint +- [#4821](https://github.com/blockscout/blockscout/pull/4821) - Block Details page: Remove crossing at the Burnt Fee line +- [#4819](https://github.com/blockscout/blockscout/pull/4819) - Add config for GasUsage Cache +- [#4781](https://github.com/blockscout/blockscout/pull/4781) - PGAnalyze index suggestions +- [#4735](https://github.com/blockscout/blockscout/pull/4735) - Code clean up: Remove clauses for outdated ganache bugs +- [#4726](https://github.com/blockscout/blockscout/pull/4726) - Update chart.js +- [#4707](https://github.com/blockscout/blockscout/pull/4707) - Top navigation: Move Accounts tab to Tokens +- [#4704](https://github.com/blockscout/blockscout/pull/4704) - Update to Erlang/OTP 24 +- [#4682](https://github.com/blockscout/blockscout/pull/4682) - Update all possible outdated mix dependencies +- [#4663](https://github.com/blockscout/blockscout/pull/4663) - Migrate to Elixir 1.12.x +- [#4661](https://github.com/blockscout/blockscout/pull/4661) - Update NPM packages to resolve vulnerabilities +- [#4649](https://github.com/blockscout/blockscout/pull/4649) - 1559 Transaction Page: Convert Burnt Fee to ether and add price in USD +- [#4646](https://github.com/blockscout/blockscout/pull/4646) - Transaction page: Rename burned to burnt +- [#4611](https://github.com/blockscout/blockscout/pull/4611) - Ability to hide miner in block views + +## 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 +- [#4528](https://github.com/blockscout/blockscout/pull/4528) - Block Details page: rework view +- [#4531](https://github.com/blockscout/blockscout/pull/4531) - Add Arbitrum support +- [#4524](https://github.com/blockscout/blockscout/pull/4524) - Add index position of transaction in the block +- [#4489](https://github.com/blockscout/blockscout/pull/4489) - Search results page +- [#4475](https://github.com/blockscout/blockscout/pull/4475) - Tx page facelifting +- [#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 +- [#4535](https://github.com/blockscout/blockscout/pull/4535) - Indexer performance update:: Eliminate multiple updates of the same token while parsing mint/burn token transfers batch +- [#4527](https://github.com/blockscout/blockscout/pull/4527) - Indexer performance update: refactor coin balance daily fetcher +- [#4525](https://github.com/blockscout/blockscout/pull/4525) - Uncataloged token transfers query performance improvement +- [#4513](https://github.com/blockscout/blockscout/pull/4513) - Fix installation with custom default path: add NETWORK_PATH variable to the current_path +- [#4500](https://github.com/blockscout/blockscout/pull/4500) - `/tokens/{addressHash}/instance/{id}/token-transfers`: fix incorrect next page url +- [#4493](https://github.com/blockscout/blockscout/pull/4493) - Contract's code page: handle null contracts_creation_transaction +- [#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 +- [#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 +- [#4516](https://github.com/blockscout/blockscout/pull/4516) - Add DB migrations step into Docker start script +- [#4497](https://github.com/blockscout/blockscout/pull/4497) - Handle error in fetch_validators_list method +- [#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 +- [#4419](https://github.com/blockscout/blockscout/pull/4419) - Order contracts in the search by inserted_at in descending order +- [#4418](https://github.com/blockscout/blockscout/pull/4418) - Fix empty search results for the full-word search criteria +- [#4406](https://github.com/blockscout/blockscout/pull/4406) - Fix internal server error on the validator's txs page +- [#4360](https://github.com/blockscout/blockscout/pull/4360) - Fix false-pending transactions in reorg blocks +- [#4388](https://github.com/blockscout/blockscout/pull/4388) - Fix internal server error on contract page for insctances without sourcify envs +- [#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` +- [#4382](https://github.com/blockscout/blockscout/pull/4382) - Replace awesomplete with autocomplete.js +- [#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 +- [#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 +- [#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 +- [#4308](https://github.com/blockscout/blockscout/pull/4308) - Fix internal server error on contract verification options page +- [#4307](https://github.com/blockscout/blockscout/pull/4307) - Fix for composing IPFS URLs for NFTs images +- [#4306](https://github.com/blockscout/blockscout/pull/4306) - Check token instance images MIME types +- [#4295](https://github.com/blockscout/blockscout/pull/4295) - Mobile view fix: transaction tile tx hash overflow +- [#4294](https://github.com/blockscout/blockscout/pull/4294) - User wont be able to open verification pages for verified smart-contract +- [#4240](https://github.com/blockscout/blockscout/pull/4240) - `[]` is accepted in write contract page +- [#4236](https://github.com/blockscout/blockscout/pull/4236), [#4242](https://github.com/blockscout/blockscout/pull/4242) - Fix typo, constructor instead of contructor +- [#4167](https://github.com/blockscout/blockscout/pull/4167) - Deduplicate block numbers in acquire_blocks function +- [#4149](https://github.com/blockscout/blockscout/pull/4149) - Exclude smart_contract_additional_sources from JSON encoding in address schema +- [#4137](https://github.com/blockscout/blockscout/pull/4137) - Get token balance query improvement +- [#4129](https://github.com/blockscout/blockscout/pull/4129) - Speedup procedure of finding missing block numbers for catchup fetcher +- [#4038](https://github.com/blockscout/blockscout/pull/4038) - Add clause for abi_decode_address_output/1 when is_nil(address) +- [#3989](https://github.com/blockscout/blockscout/pull/3989), [4061](https://github.com/blockscout/blockscout/pull/4061) - Fixed bug that sometimes lead to incorrect ordering of token transfers +- [#3946](https://github.com/blockscout/blockscout/pull/3946) - Get NFT metadata from URIs with status_code 301 +- [#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 +- [#4268](https://github.com/blockscout/blockscout/pull/4268) - Migration to Chart.js 3.0 +- [#4253](https://github.com/blockscout/blockscout/pull/4253) - Elixir 1.11.4, npm audit fix +- [#4231](https://github.com/blockscout/blockscout/pull/4231) - Transactions stats: get min/max blocks in one query +- [#4157](https://github.com/blockscout/blockscout/pull/4157) - Fix internal docs generation +- [#4127](https://github.com/blockscout/blockscout/pull/4127) - Update ex_keccak package +- [#4063](https://github.com/blockscout/blockscout/pull/4063) - Do not display 4bytes signature in the tx tile for contract creation +- [#3934](https://github.com/blockscout/blockscout/pull/3934) - Update nimble_csv package +- [#3902](https://github.com/blockscout/blockscout/pull/3902) - Increase number of left symbols in short address view +- [#3894](https://github.com/blockscout/blockscout/pull/3894) - Refactoring: replace inline style display: none with d-none class +- [#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 +- [#3786](https://github.com/blockscout/blockscout/pull/3786) - Read contract: enable methods with StateMutability: pure +- [#3758](https://github.com/blockscout/blockscout/pull/3758) - Add pool metadata display/change to Staking DApp +- [#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 +- [#3783](https://github.com/blockscout/blockscout/pull/3783) - Fix number of block confirmations +- [#3773](https://github.com/blockscout/blockscout/pull/3773) - Inventory pagination query performance improvement +- [#3767](https://github.com/blockscout/blockscout/pull/3767) - Decoded contract method input tuple reader fix +- [#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 +- [#3799](https://github.com/blockscout/blockscout/pull/3799) - Update credo, dialyxir mix packages +- [#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 +- [#3676](https://github.com/blockscout/blockscout/pull/3676) - Bridged tokens TLV in USD +- [#3674](https://github.com/blockscout/blockscout/pull/3674) - Display Sushiswap pools data +- [#3637](https://github.com/blockscout/blockscout/pull/3637) - getsourcecode API endpoint: show data for unverified contract from verified contract with the same bytecode +- [#3631](https://github.com/blockscout/blockscout/pull/3631) - Tokens search +- [#3631](https://github.com/blockscout/blockscout/pull/3631) - BSC OMNI bridge support +- [#3603](https://github.com/blockscout/blockscout/pull/3603) - Display method output parameter name at contract read page +- [#3597](https://github.com/blockscout/blockscout/pull/3597) - Show APY for delegators in Staking DApp +- [#3584](https://github.com/blockscout/blockscout/pull/3584) - Token holders API endpoint +- [#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 +- [#3732](https://github.com/blockscout/blockscout/pull/3732) - POSDAO: fix snapshotting and remove temporary code +- [#3731](https://github.com/blockscout/blockscout/pull/3731) - Handle bad gateway at pending transactions fetcher +- [#3730](https://github.com/blockscout/blockscout/pull/3730) - Set default period for average block time counter refresh interval +- [#3729](https://github.com/blockscout/blockscout/pull/3729) - Token on-demand balance fetcher: handle nil balance +- [#3728](https://github.com/blockscout/blockscout/pull/3728) - Coinprice api endpoint: handle nil rates +- [#3723](https://github.com/blockscout/blockscout/pull/3723) - Fix losing digits at value conversion back from WEI +- [#3715](https://github.com/blockscout/blockscout/pull/3715) - Pending transactions sanitizer process +- [#3710](https://github.com/blockscout/blockscout/pull/3710) - Missing @destination in bridged-tokens template +- [#3707](https://github.com/blockscout/blockscout/pull/3707) - Fetch bridged token price by address of foreign token, not by symbol +- [#3686](https://github.com/blockscout/blockscout/pull/3686) - BSC bridged tokens detection fix +- [#3683](https://github.com/blockscout/blockscout/pull/3683) - Token instance image IPFS link display fix +- [#3655](https://github.com/blockscout/blockscout/pull/3655) - Handle absence of readAll function in some old/legacy browsers +- [#3634](https://github.com/blockscout/blockscout/pull/3634) - Fix transaction decoding view: support tuple types +- [#3623](https://github.com/blockscout/blockscout/pull/3623) - Ignore unrecognized messages in bridge counter processes +- [#3622](https://github.com/blockscout/blockscout/pull/3622) - Contract reader: fix int type output Ignore unrecognized messages in bridge counter processes +- [#3621](https://github.com/blockscout/blockscout/pull/3621) - Contract reader: :binary input/output fix +- [#3620](https://github.com/blockscout/blockscout/pull/3620) - Ignore unfamiliar messages by Explorer.Staking.ContractState module +- [#3611](https://github.com/blockscout/blockscout/pull/3611) - Fix logo size +- [#3600](https://github.com/blockscout/blockscout/pull/3600) - Prevent update validator metadata with empty name from contract +- [#3592](https://github.com/blockscout/blockscout/pull/3592), [#3601](https://github.com/blockscout/blockscout/pull/3601), [#3607](https://github.com/blockscout/blockscout/pull/3607) - Contract interaction: fix nested tuples in the output view, add formatting +- [#3583](https://github.com/blockscout/blockscout/pull/3583) - Reduce RPC requests and DB changes by Staking DApp +- [#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 +- [#3719](https://github.com/blockscout/blockscout/pull/3719) - Rename ethprice API endpoint +- [#3717](https://github.com/blockscout/blockscout/pull/3717) - Update alpine-elixir-phoenix 1.11.3 +- [#3714](https://github.com/blockscout/blockscout/pull/3714) - Application announcements management: whole explorer, staking dapp +- [#3712](https://github.com/blockscout/blockscout/pull/3712) - POSDAO refactoring: use pool ID instead of staking address +- [#3709](https://github.com/blockscout/blockscout/pull/3709) - Fix 413 Request Entity Too Large returned from single request batch +- [#3708](https://github.com/blockscout/blockscout/pull/3708) - NPM 6 -> 7 +- [#3701](https://github.com/blockscout/blockscout/pull/3701) - Increase LP tokens calc process re-check interval +- [#3700](https://github.com/blockscout/blockscout/pull/3700) - Update tool versions +- [#3697](https://github.com/blockscout/blockscout/pull/3697) - Update hackney dependency +- [#3696](https://github.com/blockscout/blockscout/pull/3696) - Table loader fix +- [#3688](https://github.com/blockscout/blockscout/pull/3688) - Reorganize staking buttons +- [#3687](https://github.com/blockscout/blockscout/pull/3687) - Miscellaneous minor fixes +- [#3667](https://github.com/blockscout/blockscout/pull/3667) - Store bridged token price in the DB +- [#3662](https://github.com/blockscout/blockscout/pull/3662) - Order bridged tokens in descending order by tokens holder for Omni bridge cap calculation +- [#3659](https://github.com/blockscout/blockscout/pull/3659) - Staking Dapp new buttons: swap, bridge +- [#3645](https://github.com/blockscout/blockscout/pull/3645) - Change Twitter handle +- [#3644](https://github.com/blockscout/blockscout/pull/3644) - Correct exchange rate for SURF.finance token +- [#3618](https://github.com/blockscout/blockscout/pull/3618) - Contracts verification up to 10 libraries +- [#3616](https://github.com/blockscout/blockscout/pull/3616) - POSDAO refactoring: use zero address instead of staker address for certain cases +- [#3612](https://github.com/blockscout/blockscout/pull/3612) - POSDAO refactoring: use 'getDelegatorPools' getter instead of 'getStakerPools' in Staking DApp +- [#3585](https://github.com/blockscout/blockscout/pull/3585) - Add autoswitching from eth_subscribe to eth_blockNumber in Staking DApp +- [#3574](https://github.com/blockscout/blockscout/pull/3574) - Correct UNI token price +- [#3569](https://github.com/blockscout/blockscout/pull/3569) - Allow re-define cache period vars at runtime +- [#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 +- [#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 +- [#3515](https://github.com/blockscout/blockscout/pull/3515) - CRC total balance +- [#3513](https://github.com/blockscout/blockscout/pull/3513) - Allow square brackets for an array input data in contracts interaction +- [#3480](https://github.com/blockscout/blockscout/pull/3480) - Add support of Autonity client +- [#3470](https://github.com/blockscout/blockscout/pull/3470) - Display sum of tokens' USD value at tokens holder's address page +- [#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 +- [#3525](https://github.com/blockscout/blockscout/pull/3525), [#3533](https://github.com/blockscout/blockscout/pull/3533) - Address token balance on demand fetcher +- [#3514](https://github.com/blockscout/blockscout/pull/3514) - Read contract: fix internal server error +- [#3513](https://github.com/blockscout/blockscout/pull/3513) - Fix input data processing for method call (array type of data) +- [#3509](https://github.com/blockscout/blockscout/pull/3509) - Fix QR code tooltip appearance in mobile view +- [#3507](https://github.com/blockscout/blockscout/pull/3507), [#3510](https://github.com/blockscout/blockscout/pull/3510) - Fix left margin of balance card in mobile view +- [#3506](https://github.com/blockscout/blockscout/pull/3506) - Fix token transfer's tile styles: prevent overlapping of long names +- [#3505](https://github.com/blockscout/blockscout/pull/3505) - Fix Staking DApp first loading +- [#3433](https://github.com/blockscout/blockscout/pull/3433) - Token balances and rewards tables deadlocks elimination +- [#3494](https://github.com/blockscout/blockscout/pull/3494), [#3497](https://github.com/blockscout/blockscout/pull/3497), [#3504](https://github.com/blockscout/blockscout/pull/3504), [#3517](https://github.com/blockscout/blockscout/pull/3517) - Contracts interaction: fix method call with array[] inputs +- [#3494](https://github.com/blockscout/blockscout/pull/3494), [#3495](https://github.com/blockscout/blockscout/pull/3495) - Contracts interaction: fix tuple output display +- [#3479](https://github.com/blockscout/blockscout/pull/3479) - Fix working with big numbers in Staking DApp +- [#3477](https://github.com/blockscout/blockscout/pull/3477) - Contracts interaction: fix broken call of GnosisProxy contract methods with parameters +- [#3477](https://github.com/blockscout/blockscout/pull/3477) - Contracts interaction: fix broken call of fallback function +- [#3476](https://github.com/blockscout/blockscout/pull/3476) - Fix contract verification of precompiled contracts +- [#3467](https://github.com/blockscout/blockscout/pull/3467) - Fix Firefox styles +- [#3464](https://github.com/blockscout/blockscout/pull/3464) - Fix display of token transfers list at token page (fix unique identifier of a tile) + +- [#3457](https://github.com/blockscout/blockscout/pull/3457) - Fix endless block invalidation issue +- [#3457](https://github.com/blockscout/blockscout/pull/3457) - Fix doubled total transferred/minted/burnt tokens on transaction's page if block has reorg +- [#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 +- [#3489](https://github.com/blockscout/blockscout/pull/3489) - Migrate to Webpack@5 +- [#3487](https://github.com/blockscout/blockscout/pull/3487) - Docker setup update to be compatible with Erlang OTP 23 +- [#3484](https://github.com/blockscout/blockscout/pull/3484) - Elixir upgrade to 11.2 +- [#3483](https://github.com/blockscout/blockscout/pull/3483) - Update outdated dependencies +- [#3483](https://github.com/blockscout/blockscout/pull/3483) - Migrate to Erlang/OTP 23 +- [#3468](https://github.com/blockscout/blockscout/pull/3468) - Do not check supported networks on application loading page +- [#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 +- [#3411](https://github.com/blockscout/blockscout/pull/3411) - Circles UBI theme +- [#3406](https://github.com/blockscout/blockscout/pull/3406), [#3409](https://github.com/blockscout/blockscout/pull/3409) - Adding mp4 files support for NFTs +- [#3398](https://github.com/blockscout/blockscout/pull/3398) - Collect and display gas usage per day at the main page +- [#3385](https://github.com/blockscout/blockscout/pull/3385), [#3397](https://github.com/blockscout/blockscout/pull/3397) - Total gas usage at the main page +- [#3384](https://github.com/blockscout/blockscout/pull/3384), [#3386](https://github.com/blockscout/blockscout/pull/3386) - Address total gas usage +- [#3377](https://github.com/blockscout/blockscout/pull/3377) - Add links to contract libraries +- [#2292](https://github.com/blockscout/blockscout/pull/2292), [#3356](https://github.com/blockscout/blockscout/pull/3356), [#3359](https://github.com/blockscout/blockscout/pull/3359), [#3360](https://github.com/blockscout/blockscout/pull/3360), [#3365](https://github.com/blockscout/blockscout/pull/3365) - Add Web UI for POSDAO Staking DApp +- [#3354](https://github.com/blockscout/blockscout/pull/3354) - Tx hash in EOA coin balance history +- [#3333](https://github.com/blockscout/blockscout/pull/3333), [#3337](https://github.com/blockscout/blockscout/pull/3337), [#3393](https://github.com/blockscout/blockscout/pull/3393) - Dark forest contract custom theme +- [#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 +- [#3439](https://github.com/blockscout/blockscout/pull/3439) - Dark mode color fixes (search, charts) +- [#3437](https://github.com/blockscout/blockscout/pull/3437) - Fix Postgres Docker container +- [#3428](https://github.com/blockscout/blockscout/pull/3428) - Fix address tokens search +- [#3424](https://github.com/blockscout/blockscout/pull/3424) - Fix display of long NFT IDs +- [#3422](https://github.com/blockscout/blockscout/pull/3422) - Fix contract reader: tuple type +- [#3408](https://github.com/blockscout/blockscout/pull/3408) - Fix (total) difficulty display +- [#3401](https://github.com/blockscout/blockscout/pull/3401), [#3432](https://github.com/blockscout/blockscout/pull/3432) - Fix procedure of marking internal transactions as failed +- [#3400](https://github.com/blockscout/blockscout/pull/3400) - Add :last_block_number realtime chain event +- [#3399](https://github.com/blockscout/blockscout/pull/3399) - Fix Token transfers CSV export +- [#3396](https://github.com/blockscout/blockscout/pull/3396) - Handle exchange rates request throttled +- [#3382](https://github.com/blockscout/blockscout/pull/3382) - Check ets table exists for known tokens +- [#3376](https://github.com/blockscout/blockscout/pull/3376) - Fix contract nested inputs +- [#3375](https://github.com/blockscout/blockscout/pull/3375) - Prevent terminating of tokens/contracts process +- [#3374](https://github.com/blockscout/blockscout/pull/3374) - Fix find block timestamp query +- [#3373](https://github.com/blockscout/blockscout/pull/3373) - Fix horizontal scroll in Tokens table +- [#3370](https://github.com/blockscout/blockscout/pull/3370) - Improve contracts verification: refine constructor arguments extractor +- [#3368](https://github.com/blockscout/blockscout/pull/3368) - Fix Verify contract loading button width +- [#3357](https://github.com/blockscout/blockscout/pull/3357) - Fix token transfer realtime fetcher +- [#3353](https://github.com/blockscout/blockscout/pull/3353) - Fix xDai buttons hover color +- [#3352](https://github.com/blockscout/blockscout/pull/3352) - Fix dark body background +- [#3350](https://github.com/blockscout/blockscout/pull/3350) - Fix tokens list pagination +- [#3347](https://github.com/blockscout/blockscout/pull/3347) - Contract interaction: fix encoding of bytes output +- [#3346](https://github.com/blockscout/blockscout/pull/3346), [#3351](https://github.com/blockscout/blockscout/pull/3351) - Fix inventory tab pagination +- [#3344](https://github.com/blockscout/blockscout/pull/3344) - Fix logs search on address page +- [#3342](https://github.com/blockscout/blockscout/pull/3342) - Fix mobile styles for contract code tab +- [#3341](https://github.com/blockscout/blockscout/pull/3341) - Change Solc binary downloader path to official primary supported path +- [#3339](https://github.com/blockscout/blockscout/pull/3339) - Repair websocket subscription +- [#3329](https://github.com/blockscout/blockscout/pull/3329) - Fix pagination for bridged tokens list page +- [#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 +- [#3431](https://github.com/blockscout/blockscout/pull/3431) - Standardize token name definition, if name is empty +- [#3421](https://github.com/blockscout/blockscout/pull/3421) - Functions to enable GnosisSafe app link +- [#3414](https://github.com/blockscout/blockscout/pull/3414) - Manage lis of other explorers in the footer via env var +- [#3407](https://github.com/blockscout/blockscout/pull/3407) - Add EthereumJSONRPC.HTTP.HTTPoison.json_rpc function clause when URL is null +- [#3405](https://github.com/blockscout/blockscout/pull/3405) - N/A instead of 0 for market cap if it is not fetched +- [#3404](https://github.com/blockscout/blockscout/pull/3404) - DISABLE_KNOWN_TOKENS env var +- [#3403](https://github.com/blockscout/blockscout/pull/3403) - Refactor Coingecko interaction +- [#3394](https://github.com/blockscout/blockscout/pull/3394) - Actualize docker vars list +- [#3372](https://github.com/blockscout/blockscout/pull/3372), [#3380](https://github.com/blockscout/blockscout/pull/3380) - Improve all lists header container +- [#3371](https://github.com/blockscout/blockscout/pull/3371) - Eliminate dark background except Dark forest theme +- [#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 +- [#3282](https://github.com/blockscout/blockscout/pull/3282), [#3318](https://github.com/blockscout/blockscout/pull/3318) - Import bridged tokens custom metadata +- [#3281](https://github.com/blockscout/blockscout/pull/3281) - Write contract: display currently connected address +- [#3279](https://github.com/blockscout/blockscout/pull/3279) - NFT instance: link to the app +- [#3278](https://github.com/blockscout/blockscout/pull/3278) - Support of fetching of NFT metadata from IPFS +- [#3273](https://github.com/blockscout/blockscout/pull/3273) - Update token metadata at burn/mint events +- [#3268](https://github.com/blockscout/blockscout/pull/3268) - Token total supply on-demand fetcher +- [#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 +- [#3313](https://github.com/blockscout/blockscout/pull/3313) - Fix xDai styles: invisible tokens on address +- [#3312](https://github.com/blockscout/blockscout/pull/3312) - Replace symbol for some tokens to be able to find price in CoinGecko for OmniBridge balance +- [#3307](https://github.com/blockscout/blockscout/pull/3307) - Replace "latest" compiler version with the actual one +- [#3303](https://github.com/blockscout/blockscout/pull/3303) - Address contract twins feature performance +- [#3295](https://github.com/blockscout/blockscout/pull/3295) - Token instance: check if external_url is not null before trimming +- [#3291](https://github.com/blockscout/blockscout/pull/3291) - Support unlimited number of external rewards in block +- [#3290](https://github.com/blockscout/blockscout/pull/3290) - Eliminate protocol Jason.Encoder not implemented for... error +- [#3284](https://github.com/blockscout/blockscout/pull/3284) - Fix fetch_coin_balance query: coin balance delta +- [#3276](https://github.com/blockscout/blockscout/pull/3276) - Bridged tokens status/metadata fetcher refactoring +- [#3264](https://github.com/blockscout/blockscout/pull/3264) - Fix encoding of address output if function input exists +- [#3259](https://github.com/blockscout/blockscout/pull/3259), [#3269](https://github.com/blockscout/blockscout/pull/3269) - Contract interaction: array input type parsing fix +- [#3257](https://github.com/blockscout/blockscout/pull/3257) - Contracts read/write: method_id instead function_name as a key +- [#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 +- [#3315](https://github.com/blockscout/blockscout/pull/3315) - Environment variable to disable Bridge market cap updater +- [#3308](https://github.com/blockscout/blockscout/pull/3308) - Fixate latest stable release of Elixir, Node, Postgres +- [#3297](https://github.com/blockscout/blockscout/pull/3297) - Actualize names of default chains +- [#3285](https://github.com/blockscout/blockscout/pull/3285) - Switch to RPC endpoint polling if ETHEREUM_JSONRPC_WS_URL is an empty string +- [#3274](https://github.com/blockscout/blockscout/pull/3274) - Replace underscore with hyphen in routes +- [#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) +- [#3227](https://github.com/blockscout/blockscout/pull/3227) - Distinguishing of bridged tokens +- [#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 +- [#3233](https://github.com/blockscout/blockscout/pull/3233) - Fix for the contract verifiaction for solc 0.5 family with experimental features enabled +- [#3231](https://github.com/blockscout/blockscout/pull/3231) - Improve search: unlimited number of searching results +- [#3231](https://github.com/blockscout/blockscout/pull/3231) - Improve search: allow search with space +- [#3231](https://github.com/blockscout/blockscout/pull/3231) - Improve search: order by token holders in descending order and token/contract name is ascending order +- [#3226](https://github.com/blockscout/blockscout/pull/3226) - Fix notifier query for live update of token transfers +- [#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 +- [#3184](https://github.com/blockscout/blockscout/pull/3184) - Apps navbar menu item +- [#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 +- [#3143](https://github.com/blockscout/blockscout/pull/3143) - Fix "Connection lost..." error at address page +- [#3209](https://github.com/blockscout/blockscout/pull/3209) - GraphQL: fix internal server error at request of internal transactions at address +- [#3207](https://github.com/blockscout/blockscout/pull/3207) - Fix read contract bytes array type output +- [#3203](https://github.com/blockscout/blockscout/pull/3203) - Improve "get mined blocks" query performance +- [#3202](https://github.com/blockscout/blockscout/pull/3202) - Fix contracts verification with experimental features enabled +- [#3201](https://github.com/blockscout/blockscout/pull/3201) - Connect to Metamask button +- [#3192](https://github.com/blockscout/blockscout/pull/3192) - Dropdown menu doesn't open at "not found" page +- [#3190](https://github.com/blockscout/blockscout/pull/3190) - Contract log/method decoded view improvements: eliminate horizontal scroll, remove excess borders, whitespaces +- [#3185](https://github.com/blockscout/blockscout/pull/3185) - Transaction page: decoding logs from nested contracts calls +- [#3182](https://github.com/blockscout/blockscout/pull/3182) - Besu: support revertReason key in eth_getTransactionReceipt endpoint +- [#3178](https://github.com/blockscout/blockscout/pull/3178) - Fix permanent fetching tokens... when read/write proxy tab is active +- [#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 +- [#3204](https://github.com/blockscout/blockscout/pull/3204) - GraphQL Absinthe related packages update up to stable versions +- [#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 +- [#3161](https://github.com/blockscout/blockscout/pull/3161) - Write proxy contracts feature +- [#3160](https://github.com/blockscout/blockscout/pull/3160) - Write contracts feature +- [#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 +- [#3149](https://github.com/blockscout/blockscout/pull/3149) - Display and store revert reason of tx on demand at transaction details page and at gettxinfo API endpoint. + +### Fixes + +### Chore + +- [#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 +- [#3133](https://github.com/blockscout/blockscout/pull/3133) - Take into account FIRST_BLOCK in trace_ReplayBlockTransactions requests +- [#3132](https://github.com/blockscout/blockscout/pull/3132) - Fix performance of coin supply API endpoints +- [#3130](https://github.com/blockscout/blockscout/pull/3130) - Take into account FIRST_BLOCK for block rewards fetching +- [#3128](https://github.com/blockscout/blockscout/pull/3128) - Token instance metadata retriever refinement: add processing of token metadata if only image URL is passed to token URI +- [#3126](https://github.com/blockscout/blockscout/pull/3126) - Fetch balance only for blocks which are greater or equal block with FIRST_BLOCK number +- [#3125](https://github.com/blockscout/blockscout/pull/3125) - Fix performance of coin balance history chart +- [#3122](https://github.com/blockscout/blockscout/pull/3122) - Exclude balance percentage calculation for burn address on accounts page +- [#3121](https://github.com/blockscout/blockscout/pull/3121) - Geth: handle response from eth_getblockbyhash JSON RPC method without totalDifficulty (uncle blocks) +- [#3119](https://github.com/blockscout/blockscout/pull/3119), [#3120](https://github.com/blockscout/blockscout/pull/3120) - Fix performance of Inventory tab loading for ERC-721 tokens +- [#3114](https://github.com/blockscout/blockscout/pull/3114) - Fix performance of "Blocks validated" page +- [#3112](https://github.com/blockscout/blockscout/pull/3112) - Fix verification of contracts, compiled with nightly builds of solc compiler +- [#3112](https://github.com/blockscout/blockscout/pull/3112) - Check compiler version at contract verification +- [#3106](https://github.com/blockscout/blockscout/pull/3106) - Fix verification of contracts with `immutable` declaration +- [#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" +- [#3066](https://github.com/blockscout/blockscout/pull/3066) - ERC-721 token instance page: link to token added +- [#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 +- [#3077](https://github.com/blockscout/blockscout/pull/3077) - Finally speedup pending tx list +- [#3076](https://github.com/blockscout/blockscout/pull/3076) - Speedup tx list query on address page: check if an address has a reward, check if this is actual payout key of the validator - beneficiary, return only mined txs in tx list query +- [#3071](https://github.com/blockscout/blockscout/pull/3071) - Speedup list of token transfers per token query +- [#3070](https://github.com/blockscout/blockscout/pull/3070) - Index creation to blazingly speedup token holders query +- [#3064](https://github.com/blockscout/blockscout/pull/3064) - Automatically define Block reward contract address in TokenBridge supply module +- [#3061](https://github.com/blockscout/blockscout/pull/3061) - Fix verification of contracts with error messages in require in parent contract +- [#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 +- [#3028](https://github.com/blockscout/blockscout/pull/3028) - Decrease polling period value for realtime fetcher +- [#3027](https://github.com/blockscout/blockscout/pull/3027) - Rescue for SUPPORTED_CHAINS env var parsing +- [#3025](https://github.com/blockscout/blockscout/pull/3025) - Fix splitting of indexer/web components setup +- [#3024](https://github.com/blockscout/blockscout/pull/3024) - Fix pool size default value in config +- [#3021](https://github.com/blockscout/blockscout/pull/3021), [#3022](https://github.com/blockscout/blockscout/pull/3022) - Refine dev/test config +- [#3016](https://github.com/blockscout/blockscout/pull/3016), [#3017](https://github.com/blockscout/blockscout/pull/3017) - Fix token instance QR code data +- [#3012](https://github.com/blockscout/blockscout/pull/3012) - Speedup token transfers list query +- [#3011](https://github.com/blockscout/blockscout/pull/3011) - Revert realtime fetcher small skips feature +- [#3007](https://github.com/blockscout/blockscout/pull/3007) - Fix copy UTF8 tx input action +- [#2996](https://github.com/blockscout/blockscout/pull/2996) - Fix awesomplete lib loading in Firefox +- [#2993](https://github.com/blockscout/blockscout/pull/2993) - Fix path definition for contract verification endpoint +- [#2990](https://github.com/blockscout/blockscout/pull/2990) - Fix import of Parity spec file +- [#2989](https://github.com/blockscout/blockscout/pull/2989) - Introduce API_PATH env var +- [#2988](https://github.com/blockscout/blockscout/pull/2988) - Fix web manifest accessibility +- [#2967](https://github.com/blockscout/blockscout/pull/2967) - Fix styles loading for firefox +- [#2950](https://github.com/blockscout/blockscout/pull/2950) - Add `creationMethod` to `EthereumJSONRPC.Parity.Trace.Action.entry_to_elixir` +- [#2897](https://github.com/blockscout/blockscout/pull/2897) - remove duplicate indexes +- [#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 +- [#2960](https://github.com/blockscout/blockscout/pull/2960) - Add BLOCKSCOUT_HOST to docker setup +- [#2956](https://github.com/blockscout/blockscout/pull/2956) - Add support of 0.6.x version of compiler +- [#2955](https://github.com/blockscout/blockscout/pull/2955) - Move socket path to env +- [#2938](https://github.com/blockscout/blockscout/pull/2938) - utf8 copy tx input tooltip +- [#2934](https://github.com/blockscout/blockscout/pull/2934) - RSK release 1.2.0 breaking changes support +- [#2933](https://github.com/blockscout/blockscout/pull/2933) - Get rid of deadlock in the query to address_current_token_balance table +- [#2932](https://github.com/blockscout/blockscout/pull/2932) - fix duplicate websocket connection +- [#2928](https://github.com/blockscout/blockscout/pull/2928) - Speedup pending block ops int txs to fetch query +- [#2924](https://github.com/blockscout/blockscout/pull/2924) - Speedup address to logs query +- [#2915](https://github.com/blockscout/blockscout/pull/2915) - Speedup of blocks_without_reward_query +- [#2914](https://github.com/blockscout/blockscout/pull/2914) - Reduce execution time of stream_unfetched_token_instances query +- [#2910](https://github.com/blockscout/blockscout/pull/2910) - Reorganize queries and indexes for internal_transactions table +- [#2908](https://github.com/blockscout/blockscout/pull/2908) - Fix performance of address page +- [#2906](https://github.com/blockscout/blockscout/pull/2906) - fix address sum cache +- [#2902](https://github.com/blockscout/blockscout/pull/2902) - Offset in blocks retrieval for average block time +- [#2900](https://github.com/blockscout/blockscout/pull/2900) - check fetched instance metadata in multiple places +- [#2899](https://github.com/blockscout/blockscout/pull/2899) - fix empty buffered task +- [#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 +- [#2946](https://github.com/blockscout/blockscout/pull/2946) - Fix vulnerable NPM deps +- [#2942](https://github.com/blockscout/blockscout/pull/2942) - Actualize Docker setup +- [#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 +- [#2821](https://github.com/blockscout/blockscout/pull/2821) - add autodetection of constructor arguments +- [#2825](https://github.com/blockscout/blockscout/pull/2825) - separate token transfers and transactions +- [#2787](https://github.com/blockscout/blockscout/pull/2787) - async fetching of address counters +- [#2791](https://github.com/blockscout/blockscout/pull/2791) - add ipc client +- [#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 +- [#2851](https://github.com/blockscout/blockscout/pull/2851) - Fix paths for front assets +- [#2843](https://github.com/blockscout/blockscout/pull/2843) - fix realtime fetcher small skips feature +- [#2841](https://github.com/blockscout/blockscout/pull/2841) - LUKSO dashboard height fix +- [#2837](https://github.com/blockscout/blockscout/pull/2837) - fix txlist ordering issue +- [#2830](https://github.com/blockscout/blockscout/pull/2830) - Fix wrong color of contract icon on xDai chain +- [#2829](https://github.com/blockscout/blockscout/pull/2829) - Fix for stuck gas limit label and value +- [#2828](https://github.com/blockscout/blockscout/pull/2828) - Fix for script that clears compilation/launching assets +- [#2800](https://github.com/blockscout/blockscout/pull/2800) - return not found for not verified contract for token read_contract +- [#2806](https://github.com/blockscout/blockscout/pull/2806) - Fix blocks fetching on the main page +- [#2803](https://github.com/blockscout/blockscout/pull/2803) - Fix block validator custom tooltip +- [#2748](https://github.com/blockscout/blockscout/pull/2748) - Rewrite token updater +- [#2704](https://github.com/blockscout/blockscout/pull/2704) - refetch null values in token balances +- [#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 +- [#2845](https://github.com/blockscout/blockscout/pull/2845) - Set outline none for nav dropdown item in mobile view (fix for Safari) +- [#2844](https://github.com/blockscout/blockscout/pull/2844) - Extend external reward types up to 20 +- [#2827](https://github.com/blockscout/blockscout/pull/2827) - Node js 12.13.0 (latest LTS release) support +- [#2818](https://github.com/blockscout/blockscout/pull/2818) - allow hiding marketcap percentage +- [#2817](https://github.com/blockscout/blockscout/pull/2817) - move docker integration documentation to blockscout docs +- [#2808](https://github.com/blockscout/blockscout/pull/2808) - Add tooltip for tx input +- [#2807](https://github.com/blockscout/blockscout/pull/2807) - 422 page +- [#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 +- [#2735](https://github.com/blockscout/blockscout/pull/2735) - Add pending transactions cache +- [#2726](https://github.com/blockscout/blockscout/pull/2726) - Remove internal_transaction block_number setting from blocks runner +- [#2717](https://github.com/blockscout/blockscout/pull/2717) - Improve speed of nonconsensus data removal +- [#2679](https://github.com/blockscout/blockscout/pull/2679) - added fixed height for card chain blocks and card chain transactions +- [#2678](https://github.com/blockscout/blockscout/pull/2678) - fixed dashboard banner height bug +- [#2672](https://github.com/blockscout/blockscout/pull/2672) - added new theme for xUSDT +- [#2667](https://github.com/blockscout/blockscout/pull/2667) - Add ETS-based cache for accounts page +- [#2666](https://github.com/blockscout/blockscout/pull/2666) - fetch token counters in parallel +- [#2665](https://github.com/blockscout/blockscout/pull/2665) - new menu layout for mobile devices +- [#2663](https://github.com/blockscout/blockscout/pull/2663) - Fetch address counters in parallel +- [#2642](https://github.com/blockscout/blockscout/pull/2642) - add ERC721 coin instance page +- [#2762](https://github.com/blockscout/blockscout/pull/2762) - on-fly fetching of token instances +- [#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 +- [#2799](https://github.com/blockscout/blockscout/pull/2799) - fix catchup fetcher for empty node and db +- [#2783](https://github.com/blockscout/blockscout/pull/2783) - Fix stuck value and ticker on the token page +- [#2781](https://github.com/blockscout/blockscout/pull/2781) - optimize txlist json rpc +- [#2777](https://github.com/blockscout/blockscout/pull/2777) - Remove duplicate blocks from changes_list before import +- [#2770](https://github.com/blockscout/blockscout/pull/2770) - do not re-fetch token instances without uris +- [#2769](https://github.com/blockscout/blockscout/pull/2769) - optimize token token transfers query +- [#2768](https://github.com/blockscout/blockscout/pull/2768) - Remove nonconsensus blocks from cache after internal transactions importing +- [#2761](https://github.com/blockscout/blockscout/pull/2761) - add indexes for token instances fetching queries +- [#2767](https://github.com/blockscout/blockscout/pull/2767) - fix websocket subscriptions with token instances +- [#2765](https://github.com/blockscout/blockscout/pull/2765) - fixed width issue for cards in mobile view for Transaction Details page +- [#2755](https://github.com/blockscout/blockscout/pull/2755) - various token instance fetcher fixes +- [#2753](https://github.com/blockscout/blockscout/pull/2753) - fix nft token instance images +- [#2750](https://github.com/blockscout/blockscout/pull/2750) - fixed contract buttons color for NFT token instance on each theme +- [#2746](https://github.com/blockscout/blockscout/pull/2746) - fixed wrong alignment in logs decoded view +- [#2745](https://github.com/blockscout/blockscout/pull/2745) - optimize addresses page +- [#2742](https://github.com/blockscout/blockscout/pull/2742) - +fixed menu hovers in dark mode desktop view +- [#2737](https://github.com/blockscout/blockscout/pull/2737) - switched hardcoded subnetwork value to elixir expression for mobile menu +- [#2736](https://github.com/blockscout/blockscout/pull/2736) - do not update cache if no blocks were inserted +- [#2731](https://github.com/blockscout/blockscout/pull/2731) - fix library verification +- [#2718](https://github.com/blockscout/blockscout/pull/2718) - Include all addresses taking part in transactions in wallets' addresses counter +- [#2709](https://github.com/blockscout/blockscout/pull/2709) - Fix stuck label and value for uncle block height +- [#2707](https://github.com/blockscout/blockscout/pull/2707) - fix for dashboard banner chart legend items +- [#2706](https://github.com/blockscout/blockscout/pull/2706) - fix empty total_supply in coin gecko response +- [#2701](https://github.com/blockscout/blockscout/pull/2701) - Exclude nonconsensus blocks from avg block time calculation by default +- [#2696](https://github.com/blockscout/blockscout/pull/2696) - do not update fetched_coin_balance with nil +- [#2693](https://github.com/blockscout/blockscout/pull/2693) - remove non consensus internal transactions +- [#2691](https://github.com/blockscout/blockscout/pull/2691) - fix exchange rate websocket update for Rootstock +- [#2688](https://github.com/blockscout/blockscout/pull/2688) - fix try it out section +- [#2687](https://github.com/blockscout/blockscout/pull/2687) - remove non-consensus token transfers, logs when inserting new consensus blocks +- [#2684](https://github.com/blockscout/blockscout/pull/2684) - do not filter pending logs +- [#2682](https://github.com/blockscout/blockscout/pull/2682) - Use Task.start instead of Task.async in caches +- [#2671](https://github.com/blockscout/blockscout/pull/2671) - fixed buttons color at smart contract section +- [#2660](https://github.com/blockscout/blockscout/pull/2660) - set correct last value for coin balances chart data +- [#2619](https://github.com/blockscout/blockscout/pull/2619) - Enforce DB transaction's order to prevent deadlocks +- [#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 +- [#2789](https://github.com/blockscout/blockscout/pull/2786) - remove projects table in readme, link to docs version +- [#2786](https://github.com/blockscout/blockscout/pull/2786) - updated docs links, removed docs folder +- [#2752](https://github.com/blockscout/blockscout/pull/2752) - allow enabling internal transactions for simple token transfers txs +- [#2749](https://github.com/blockscout/blockscout/pull/2749) - fix opt 22.1 support +- [#2744](https://github.com/blockscout/blockscout/pull/2744) - Disable Geth tests in CI +- [#2724](https://github.com/blockscout/blockscout/pull/2724) - fix ci by commenting a line in hackney library +- [#2708](https://github.com/blockscout/blockscout/pull/2708) - add log index to logs 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 +- [#2505](https://github.com/blockscout/blockscout/pull/2505) - support POA Network emission rewards +- [#2581](https://github.com/blockscout/blockscout/pull/2581) - Add generic Map-like Cache behaviour and implementation +- [#2561](https://github.com/blockscout/blockscout/pull/2561) - Add token's type to the response of tokenlist method +- [#2555](https://github.com/blockscout/blockscout/pull/2555) - find and show decoding candidates for logs +- [#2499](https://github.com/blockscout/blockscout/pull/2499) - import emission reward ranges +- [#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 +- [#2626](https://github.com/blockscout/blockscout/pull/2626) - Fixing 2 Mobile UI Issues +- [#2623](https://github.com/blockscout/blockscout/pull/2623) - fix a blinking test +- [#2616](https://github.com/blockscout/blockscout/pull/2616) - deduplicate coin history records by delta +- [#2613](https://github.com/blockscout/blockscout/pull/2613) - fix getminedblocks rpc endpoint +- [#2612](https://github.com/blockscout/blockscout/pull/2612) - Add cache updating independently from Indexer +- [#2610](https://github.com/blockscout/blockscout/pull/2610) - use CoinGecko instead of CoinMarketcap for exchange rates +- [#2592](https://github.com/blockscout/blockscout/pull/2592) - process new metadata format for whisper +- [#2591](https://github.com/blockscout/blockscout/pull/2591) - Fix url error in API page +- [#2572](https://github.com/blockscout/blockscout/pull/2572) - Ease non-critical css +- [#2570](https://github.com/blockscout/blockscout/pull/2570) - Network icons preload +- [#2569](https://github.com/blockscout/blockscout/pull/2569) - do not fetch emission rewards for transactions csv exporter +- [#2568](https://github.com/blockscout/blockscout/pull/2568) - filter pending token transfers +- [#2564](https://github.com/blockscout/blockscout/pull/2564) - fix first page button for uncles and reorgs +- [#2563](https://github.com/blockscout/blockscout/pull/2563) - Fix view less transfers button +- [#2538](https://github.com/blockscout/blockscout/pull/2538) - fetch the last not empty coin balance records +- [#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 +- [#2617](https://github.com/blockscout/blockscout/pull/2617) - skip cache update if there are no blocks inserted +- [#2611](https://github.com/blockscout/blockscout/pull/2611) - fix js dependency vulnerabilities +- [#2594](https://github.com/blockscout/blockscout/pull/2594) - do not start genesis data fetching periodically +- [#2590](https://github.com/blockscout/blockscout/pull/2590) - restore backward compatablity with old releases +- [#2577](https://github.com/blockscout/blockscout/pull/2577) - Need recompile column in the env vars table +- [#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 +- [#2477](https://github.com/blockscout/blockscout/pull/2477) - aggregate token transfers on transaction page +- [#2458](https://github.com/blockscout/blockscout/pull/2458) - Add LAST_BLOCK var to add ability indexing in the range of blocks +- [#2456](https://github.com/blockscout/blockscout/pull/2456) - fetch pending transactions for geth +- [#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 +- [#2553](https://github.com/blockscout/blockscout/pull/2553) - Dark theme import to the end of sass +- [#2550](https://github.com/blockscout/blockscout/pull/2550) - correctly encode decimal values for frontend +- [#2549](https://github.com/blockscout/blockscout/pull/2549) - Fix wrong colour of tooltip +- [#2548](https://github.com/blockscout/blockscout/pull/2548) - CSS preload support in Firefox +- [#2547](https://github.com/blockscout/blockscout/pull/2547) - do not show eth value if it's zero on the transaction overview page +- [#2543](https://github.com/blockscout/blockscout/pull/2543) - do not hide search input during logs search +- [#2524](https://github.com/blockscout/blockscout/pull/2524) - fix dark theme validator data styles +- [#2532](https://github.com/blockscout/blockscout/pull/2532) - don't show empty token transfers on the transaction overview page +- [#2528](https://github.com/blockscout/blockscout/pull/2528) - fix coin history chart data +- [#2520](https://github.com/blockscout/blockscout/pull/2520) - Hide loading message when fetching is failed +- [#2523](https://github.com/blockscout/blockscout/pull/2523) - Avoid importing internal_transactions of pending transactions +- [#2519](https://github.com/blockscout/blockscout/pull/2519) - enable `First` page button in pagination +- [#2518](https://github.com/blockscout/blockscout/pull/2518) - create suggested indexes +- [#2517](https://github.com/blockscout/blockscout/pull/2517) - remove duplicate indexes +- [#2515](https://github.com/blockscout/blockscout/pull/2515) - do not aggregate NFT token transfers +- [#2514](https://github.com/blockscout/blockscout/pull/2514) - Isolating of staking dapp css && extracting of non-critical css +- [#2512](https://github.com/blockscout/blockscout/pull/2512) - alert link fix +- [#2509](https://github.com/blockscout/blockscout/pull/2509) - value-ticker gaps fix +- [#2508](https://github.com/blockscout/blockscout/pull/2508) - logs view columns fix +- [#2506](https://github.com/blockscout/blockscout/pull/2506) - fix two active tab in the top menu +- [#2503](https://github.com/blockscout/blockscout/pull/2503) - Mitigate autocompletion library influence to page loading performance +- [#2502](https://github.com/blockscout/blockscout/pull/2502) - increase reward task timeout +- [#2463](https://github.com/blockscout/blockscout/pull/2463) - dark theme fixes +- [#2496](https://github.com/blockscout/blockscout/pull/2496) - fix docker build +- [#2495](https://github.com/blockscout/blockscout/pull/2495) - fix logs for indexed chain +- [#2459](https://github.com/blockscout/blockscout/pull/2459) - fix top addresses query +- [#2425](https://github.com/blockscout/blockscout/pull/2425) - Force to show address view for checksummed address even if it is not in DB +- [#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 +- [#2516](https://github.com/blockscout/blockscout/pull/2516) - update absinthe plug from fork +- [#2473](https://github.com/blockscout/blockscout/pull/2473) - get rid of cldr warnings +- [#2402](https://github.com/blockscout/blockscout/pull/2402) - bump otp version to 22.0 +- [#2492](https://github.com/blockscout/blockscout/pull/2492) - hide decoded row if event is not decoded +- [#2490](https://github.com/blockscout/blockscout/pull/2490) - enable credo duplicated code check +- [#2432](https://github.com/blockscout/blockscout/pull/2432) - bump credo version +- [#2457](https://github.com/blockscout/blockscout/pull/2457) - update mix.lock +- [#2435](https://github.com/blockscout/blockscout/pull/2435) - Replace deprecated extract-text-webpack-plugin with mini-css-extract-plugin +- [#2450](https://github.com/blockscout/blockscout/pull/2450) - Fix clearance of logs and node_modules folders in clearing script +- [#2434](https://github.com/blockscout/blockscout/pull/2434) - get rid of timex warnings +- [#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 +- [#2379](https://github.com/blockscout/blockscout/pull/2379) - Disable network selector when is empty +- [#2374](https://github.com/blockscout/blockscout/pull/2374) - decode constructor arguments for verified smart contracts +- [#2366](https://github.com/blockscout/blockscout/pull/2366) - paginate eth logs +- [#2360](https://github.com/blockscout/blockscout/pull/2360) - add default evm version to smart contract verification +- [#2352](https://github.com/blockscout/blockscout/pull/2352) - Fetch rewards in parallel with transactions +- [#2294](https://github.com/blockscout/blockscout/pull/2294) - add healthy block period checking endpoint +- [#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 +- [#2413](https://github.com/blockscout/blockscout/pull/2413) - remove outer tables for decoded data +- [#2410](https://github.com/blockscout/blockscout/pull/2410) - preload smart contract for logs decoding +- [#2405](https://github.com/blockscout/blockscout/pull/2405) - added templates for table loader and tile loader +- [#2398](https://github.com/blockscout/blockscout/pull/2398) - show only one decoded candidate +- [#2389](https://github.com/blockscout/blockscout/pull/2389) - Reduce Lodash lib size (86% of lib methods are not used) +- [#2388](https://github.com/blockscout/blockscout/pull/2388) - add create2 support to geth's js tracer +- [#2387](https://github.com/blockscout/blockscout/pull/2387) - fix not existing keys in transaction json rpc +- [#2378](https://github.com/blockscout/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css +- [#2368](https://github.com/blockscout/blockscout/pull/2368) - add two columns of smart contract info +- [#2375](https://github.com/blockscout/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict +- [#2346](https://github.com/blockscout/blockscout/pull/2346) - Avoid fetching internal transactions of blocks that still need refetching +- [#2350](https://github.com/blockscout/blockscout/pull/2350) - fix invalid User agent headers +- [#2345](https://github.com/blockscout/blockscout/pull/2345) - do not override existing market records +- [#2337](https://github.com/blockscout/blockscout/pull/2337) - set url params for prod explicitly +- [#2341](https://github.com/blockscout/blockscout/pull/2341) - fix transaction input json encoding +- [#2311](https://github.com/blockscout/blockscout/pull/2311) - fix market history overriding with zeroes +- [#2310](https://github.com/blockscout/blockscout/pull/2310) - parse url for api docs +- [#2299](https://github.com/blockscout/blockscout/pull/2299) - fix interpolation in error message +- [#2303](https://github.com/blockscout/blockscout/pull/2303) - fix transaction csv download link +- [#2304](https://github.com/blockscout/blockscout/pull/2304) - footer grid fix for md resolution +- [#2291](https://github.com/blockscout/blockscout/pull/2291) - dashboard fix for md resolution, transactions load fix, block info row fix, addresses page issue, check mark issue +- [#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 +- [#2363](https://github.com/blockscout/blockscout/pull/2363) - add parameters example for eth rpc +- [#2342](https://github.com/blockscout/blockscout/pull/2342) - Upgrade Postgres image version in Docker setup +- [#2325](https://github.com/blockscout/blockscout/pull/2325) - Reduce function input to address' hash only where possible +- [#2323](https://github.com/blockscout/blockscout/pull/2323) - Group Explorer caches +- [#2305](https://github.com/blockscout/blockscout/pull/2305) - Improve Address controllers +- [#2302](https://github.com/blockscout/blockscout/pull/2302) - fix names for xDai source +- [#2289](https://github.com/blockscout/blockscout/pull/2289) - Optional websockets for dev environment +- [#2307](https://github.com/blockscout/blockscout/pull/2307) - add GoJoy to README +- [#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 +- [#2075](https://github.com/blockscout/blockscout/pull/2075) - add blocks cache +- [#2151](https://github.com/blockscout/blockscout/pull/2151) - hide dropdown menu then other networks list is empty +- [#2191](https://github.com/blockscout/blockscout/pull/2191) - allow to configure token metadata update interval +- [#2146](https://github.com/blockscout/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint +- [#2216](https://github.com/blockscout/blockscout/pull/2216) - Improve token's controllers by avoiding unnecessary preloads +- [#2235](https://github.com/blockscout/blockscout/pull/2235) - save and show additional validation fields to smart contract +- [#2190](https://github.com/blockscout/blockscout/pull/2190) - show all token transfers +- [#2193](https://github.com/blockscout/blockscout/pull/2193) - feat: add BLOCKSCOUT_HOST, and use it in API docs +- [#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 +- [#2244](https://github.com/blockscout/blockscout/pull/2244) - fix internal transactions failing to be indexed because of constraint +- [#2281](https://github.com/blockscout/blockscout/pull/2281) - typo issues, dropdown issues +- [#2278](https://github.com/blockscout/blockscout/pull/2278) - increase threshold for scientific notation +- [#2275](https://github.com/blockscout/blockscout/pull/2275) - Description for networks selector +- [#2263](https://github.com/blockscout/blockscout/pull/2263) - added an ability to close network selector on outside click +- [#2257](https://github.com/blockscout/blockscout/pull/2257) - 'download csv' button added to different tabs +- [#2242](https://github.com/blockscout/blockscout/pull/2242) - added styles for 'download csv' button +- [#2261](https://github.com/blockscout/blockscout/pull/2261) - header logo aligned to the center properly +- [#2254](https://github.com/blockscout/blockscout/pull/2254) - search length issue, tile link wrapping issue +- [#2238](https://github.com/blockscout/blockscout/pull/2238) - header content alignment issue, hide navbar on outside click +- [#2229](https://github.com/blockscout/blockscout/pull/2229) - gap issue between qr and copy button in token transfers, top cards width and height issue +- [#2201](https://github.com/blockscout/blockscout/pull/2201) - footer columns fix +- [#2179](https://github.com/blockscout/blockscout/pull/2179) - fix docker build error +- [#2165](https://github.com/blockscout/blockscout/pull/2165) - sort blocks by timestamp when calculating average block time +- [#2175](https://github.com/blockscout/blockscout/pull/2175) - fix coinmarketcap response errors +- [#2164](https://github.com/blockscout/blockscout/pull/2164) - fix large numbers in balance view card +- [#2155](https://github.com/blockscout/blockscout/pull/2155) - fix pending transaction query +- [#2183](https://github.com/blockscout/blockscout/pull/2183) - tile content aligning for mobile resolution fix, dai logo fix +- [#2162](https://github.com/blockscout/blockscout/pull/2162) - contract creation tile color changed +- [#2144](https://github.com/blockscout/blockscout/pull/2144) - 'page not found' images path fixed for goerli +- [#2142](https://github.com/blockscout/blockscout/pull/2142) - Removed posdao theme and logo, added 'page not found' image for goerli +- [#2138](https://github.com/blockscout/blockscout/pull/2138) - badge colors issue, api titles issue +- [#2129](https://github.com/blockscout/blockscout/pull/2129) - Fix for width of explorer elements +- [#2121](https://github.com/blockscout/blockscout/pull/2121) - Binding of 404 page +- [#2120](https://github.com/blockscout/blockscout/pull/2120) - footer links and socials focus color issue +- [#2113](https://github.com/blockscout/blockscout/pull/2113) - renewed logos for rsk, dai, blockscout; themes color changes for lukso; error images for lukso +- [#2112](https://github.com/blockscout/blockscout/pull/2112) - themes color improvements, dropdown color issue +- [#2110](https://github.com/blockscout/blockscout/pull/2110) - themes colors issues, ui issues +- [#2103](https://github.com/blockscout/blockscout/pull/2103) - ui issues for all themes +- [#2090](https://github.com/blockscout/blockscout/pull/2090) - updated some ETC theme colors +- [#2096](https://github.com/blockscout/blockscout/pull/2096) - RSK theme fixes +- [#2093](https://github.com/blockscout/blockscout/pull/2093) - detect token transfer type for deprecated erc721 spec +- [#2111](https://github.com/blockscout/blockscout/pull/2111) - improve address transaction controller +- [#2108](https://github.com/blockscout/blockscout/pull/2108) - fix uncle fetching without full transactions +- [#2128](https://github.com/blockscout/blockscout/pull/2128) - add new function clause for uncle errors +- [#2123](https://github.com/blockscout/blockscout/pull/2123) - fix coins percentage view +- [#2119](https://github.com/blockscout/blockscout/pull/2119) - fix map logging +- [#2130](https://github.com/blockscout/blockscout/pull/2130) - fix navigation +- [#2148](https://github.com/blockscout/blockscout/pull/2148) - filter pending logs +- [#2147](https://github.com/blockscout/blockscout/pull/2147) - add rsk format of checksum +- [#2149](https://github.com/blockscout/blockscout/pull/2149) - remove pending transaction count +- [#2177](https://github.com/blockscout/blockscout/pull/2177) - remove duplicate entries from UncleBlock's Fetcher +- [#2169](https://github.com/blockscout/blockscout/pull/2169) - add more validator reward types for xDai +- [#2173](https://github.com/blockscout/blockscout/pull/2173) - handle correctly empty transactions +- [#2174](https://github.com/blockscout/blockscout/pull/2174) - fix reward channel joining +- [#2186](https://github.com/blockscout/blockscout/pull/2186) - fix net version test +- [#2196](https://github.com/blockscout/blockscout/pull/2196) - Nethermind client fixes +- [#2237](https://github.com/blockscout/blockscout/pull/2237) - fix rsk total_supply +- [#2198](https://github.com/blockscout/blockscout/pull/2198) - reduce transaction status and error constraint +- [#2167](https://github.com/blockscout/blockscout/pull/2167) - feat: document eth rpc api mimicking endpoints +- [#2225](https://github.com/blockscout/blockscout/pull/2225) - fix metadata decoding in Solidity 0.5.9 smart contract verification +- [#2204](https://github.com/blockscout/blockscout/pull/2204) - fix large contract verification +- [#2258](https://github.com/blockscout/blockscout/pull/2258) - reduce BlocksTransactionsMismatch memory footprint +- [#2247](https://github.com/blockscout/blockscout/pull/2247) - hide logs search if there are no logs +- [#2248](https://github.com/blockscout/blockscout/pull/2248) - sort block after query execution for average block time +- [#2249](https://github.com/blockscout/blockscout/pull/2249) - More transaction controllers improvements +- [#2267](https://github.com/blockscout/blockscout/pull/2267) - Modify implementation of `where_transaction_has_multiple_internal_transactions` +- [#2270](https://github.com/blockscout/blockscout/pull/2270) - Remove duplicate params in `Indexer.Fetcher.TokenBalance` +- [#2268](https://github.com/blockscout/blockscout/pull/2268) - remove not existing assigns in html code +- [#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 +- [#1726](https://github.com/blockscout/blockscout/pull/2071) - Updated styles for the new smart contract page. +- [#2081](https://github.com/blockscout/blockscout/pull/2081) - Tooltip for 'more' button, explorers logos added +- [#2010](https://github.com/blockscout/blockscout/pull/2010) - added "block not found" and "tx not found pages" +- [#1928](https://github.com/blockscout/blockscout/pull/1928) - pagination styles were updated +- [#1940](https://github.com/blockscout/blockscout/pull/1940) - qr modal button and background issue +- [#1907](https://github.com/blockscout/blockscout/pull/1907) - dropdown color bug fix (lukso theme) and tooltip color bug fix +- [#1859](https://github.com/blockscout/blockscout/pull/1859) - feat: show raw transaction traces +- [#1941](https://github.com/blockscout/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc +- [#1957](https://github.com/blockscout/blockscout/pull/1957) - Calculate stakes ratio before insert pools +- [#1956](https://github.com/blockscout/blockscout/pull/1956) - add logs tab to address +- [#1952](https://github.com/blockscout/blockscout/pull/1952) - feat: exclude empty contracts by default +- [#1954](https://github.com/blockscout/blockscout/pull/1954) - feat: use creation init on self destruct +- [#2036](https://github.com/blockscout/blockscout/pull/2036) - New tables for staking pools and delegators +- [#1974](https://github.com/blockscout/blockscout/pull/1974) - feat: previous page button logic +- [#1999](https://github.com/blockscout/blockscout/pull/1999) - load data async on addresses page +- [#1807](https://github.com/blockscout/blockscout/pull/1807) - New theming capabilites. +- [#2040](https://github.com/blockscout/blockscout/pull/2040) - Verification links to other explorers for ETH +- [#2037](https://github.com/blockscout/blockscout/pull/2037) - add address logs search functionality +- [#2012](https://github.com/blockscout/blockscout/pull/2012) - make all pages pagination async +- [#2064](https://github.com/blockscout/blockscout/pull/2064) - feat: add fields to tx apis, small cleanups +- [#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 +- [#2099](https://github.com/blockscout/blockscout/pull/2099) - logs search input width +- [#2098](https://github.com/blockscout/blockscout/pull/2098) - nav dropdown issue, logo size issue +- [#2082](https://github.com/blockscout/blockscout/pull/2082) - dropdown styles, tooltip gap fix, 404 page added +- [#2077](https://github.com/blockscout/blockscout/pull/2077) - ui issues +- [#2072](https://github.com/blockscout/blockscout/pull/2072) - Fixed checkmarks not showing correctly in tabs. +- [#2066](https://github.com/blockscout/blockscout/pull/2066) - fixed length of logs search input +- [#2056](https://github.com/blockscout/blockscout/pull/2056) - log search form styles added +- [#2043](https://github.com/blockscout/blockscout/pull/2043) - Fixed modal dialog width for 'verify other explorers' +- [#2025](https://github.com/blockscout/blockscout/pull/2025) - Added a new color to display transactions' errors. +- [#2033](https://github.com/blockscout/blockscout/pull/2033) - Header nav. dropdown active element color issue +- [#2019](https://github.com/blockscout/blockscout/pull/2019) - Fixed the missing tx hashes. +- [#2020](https://github.com/blockscout/blockscout/pull/2020) - Fixed a bug triggered when a second click to a selected tab caused the other tabs to hide. +- [#1944](https://github.com/blockscout/blockscout/pull/1944) - fixed styles for token's dropdown. +- [#1926](https://github.com/blockscout/blockscout/pull/1926) - status label alignment +- [#1849](https://github.com/blockscout/blockscout/pull/1849) - Improve chains menu +- [#1868](https://github.com/blockscout/blockscout/pull/1868) - fix: logs list endpoint performance +- [#1822](https://github.com/blockscout/blockscout/pull/1822) - Fix style breaks in decompiled contract code view +- [#1885](https://github.com/blockscout/blockscout/pull/1885) - highlight reserved words in decompiled code +- [#1896](https://github.com/blockscout/blockscout/pull/1896) - re-query tokens in top nav automplete +- [#1905](https://github.com/blockscout/blockscout/pull/1905) - fix reorgs, uncles pagination +- [#1904](https://github.com/blockscout/blockscout/pull/1904) - fix `BLOCK_COUNT_CACHE_TTL` env var type +- [#1915](https://github.com/blockscout/blockscout/pull/1915) - fallback to 2 latest evm versions +- [#1937](https://github.com/blockscout/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it +- [#1960](https://github.com/blockscout/blockscout/pull/1960) - do not remove bold text in decompiled contacts +- [#1966](https://github.com/blockscout/blockscout/pull/1966) - fix: add fields for contract filter performance +- [#2017](https://github.com/blockscout/blockscout/pull/2017) - fix: fix to/from filters on tx list pages +- [#2008](https://github.com/blockscout/blockscout/pull/2008) - add new function clause for xDai network beneficiaries +- [#2009](https://github.com/blockscout/blockscout/pull/2009) - addresses page improvements +- [#2027](https://github.com/blockscout/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions +- [#2062](https://github.com/blockscout/blockscout/pull/2062) - fix: uniq by hash, instead of transaction +- [#2052](https://github.com/blockscout/blockscout/pull/2052) - allow bytes32 for name and symbol +- [#2047](https://github.com/blockscout/blockscout/pull/2047) - fix: show creating internal transactions +- [#2014](https://github.com/blockscout/blockscout/pull/2014) - fix: use better queries for listLogs endpoint +- [#2027](https://github.com/blockscout/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions +- [#2070](https://github.com/blockscout/blockscout/pull/2070) - reduce `max_concurrency` of `BlocksTransactionsMismatch` fetcher +- [#2083](https://github.com/blockscout/blockscout/pull/2083) - allow total_difficuly to be nil +- [#2086](https://github.com/blockscout/blockscout/pull/2086) - fix geth's staticcall without output + +### Chore + +- [#1900](https://github.com/blockscout/blockscout/pull/1900) - SUPPORTED_CHAINS ENV var +- [#1958](https://github.com/blockscout/blockscout/pull/1958) - Default value for release link env var +- [#1964](https://github.com/blockscout/blockscout/pull/1964) - ALLOWED_EVM_VERSIONS env var +- [#1975](https://github.com/blockscout/blockscout/pull/1975) - add log index to transaction view +- [#1988](https://github.com/blockscout/blockscout/pull/1988) - Fix wrong parity tasks names in Circle CI +- [#2000](https://github.com/blockscout/blockscout/pull/2000) - docker/Makefile: always set a container name +- [#2018](https://github.com/blockscout/blockscout/pull/2018) - Use PORT env variable in dev config +- [#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 + +- [#1857](https://github.com/blockscout/blockscout/pull/1857) - Re-implement Geth JS internal transaction tracer in Elixir +- [#1989](https://github.com/blockscout/blockscout/pull/1989) - fix: consolidate address w/ balance one at a time +- [#2002](https://github.com/blockscout/blockscout/pull/2002) - Get estimated count of blocks when cache is empty + +### Fixes + +- [#1869](https://github.com/blockscout/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth +- [#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 +- [#1920](https://github.com/blockscout/blockscout/pull/1920) - fix: remove source code fields from list endpoint +- [#1876](https://github.com/blockscout/blockscout/pull/1876) - async calculate a count of blocks + +### Fixes + +- [#1917](https://github.com/blockscout/blockscout/pull/1917) - Force block refetch if transaction is re-collated in a different block + +### Chore + +- [#1892](https://github.com/blockscout/blockscout/pull/1892) - Remove temporary worker modules + +## 1.3.13-beta + +### Features + +- [#1933](https://github.com/blockscout/blockscout/pull/1933) - add eth_BlockNumber json rpc method + +### Fixes + +- [#1875](https://github.com/blockscout/blockscout/pull/1875) - fix: resolve false positive constructor arguments +- [#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 + +- [#1815](https://github.com/blockscout/blockscout/pull/1815) - Be able to search without prefix "0x" +- [#1813](https://github.com/blockscout/blockscout/pull/1813) - Add total blocks counter to the main page +- [#1806](https://github.com/blockscout/blockscout/pull/1806) - Verify contracts with a post request +- [#1848](https://github.com/blockscout/blockscout/pull/1848) - Add cache for block counter + +### Fixes + +- [#1829](https://github.com/blockscout/blockscout/pull/1829) - Handle nil quantities in block decoding routine +- [#1830](https://github.com/blockscout/blockscout/pull/1830) - Make block size field nullable +- [#1840](https://github.com/blockscout/blockscout/pull/1840) - Handle case when total supply is nil +- [#1838](https://github.com/blockscout/blockscout/pull/1838) - Block counter calculates only consensus blocks + +### Chore + +- [#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 + +- [#1739](https://github.com/blockscout/blockscout/pull/1739) - highlight decompiled source code +- [#1696](https://github.com/blockscout/blockscout/pull/1696) - full-text search by tokens +- [#1742](https://github.com/blockscout/blockscout/pull/1742) - Support RSK +- [#1777](https://github.com/blockscout/blockscout/pull/1777) - show ERC-20 token transfer info on transaction page +- [#1770](https://github.com/blockscout/blockscout/pull/1770) - set a websocket keepalive from config +- [#1789](https://github.com/blockscout/blockscout/pull/1789) - add ERC-721 info to transaction overview page +- [#1801](https://github.com/blockscout/blockscout/pull/1801) - Staking pools fetching + +### 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 + +### 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 + +## 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 + +### 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 + +### Chore + +- [#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 + +### 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 + +### 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 + +### Chore + +- [#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 + +### 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 + +### 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 + +## 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 + +### 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 + +### 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 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..bfc85b0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at andrew@poa.network. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..122503b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +## Contributing + +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'`) +5. Push to the branch (`git push origin my-new-feature`) +6. Create a new Pull Request +7. Update CHANGELOG.md with the link to PR and description of the changes + +### General + +* Commits should be one logical change that still allows all tests to pass. Prefer smaller commits if there could be two levels of logic grouping. The goal is to allow contributors in the future (including your own future self) to determine your reasoning for making changes and to allow them to cherry-pick, patch or port those changes in isolation to other branches or forks. +* If during your PR you reveal a pre-existing bug: + 1. Try to isolate the bug and fix it on an independent branch and PR it first. + 2. Try to fix the bug in a separate commit from other changes: + 1. Commit the code in the broken state that revealed the bug originally + 2. Commit the fix for the bug. + 3. Continue original PR work. + +### 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 + +### Bug Fixes + +For bug fixes, whenever possible, there should be at least 2 commits: + +1. A regression test commit that contains tests that demonstrate the bug and show as failing. +2. The bug fix commit that shows the regression test now passing. + +This format ensures that we can run the test to reproduce the original bug without depending on the new code in the fix, which could lead to the test falsely passing. + +### Incompatible Changes + +Incompatible changes can arise as a side-effect of either Enhancements or Bug Fixes. During Enhancements, incompatible changes can occur because, as an example, in order to support showing end-users new data, the database schema may need to be changed and the index rebuilt from scratch. During bug fixes, incompatible changes can occur because in order to fix a bug, the schema had to change, or how certain internal APIs are called changed. + +* 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 + ``` + +### Pull Request + +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 + * What was fixed for bugs + * What was added for enhancements + * What was changed for incompatible changes + +See [#255](https://github.com/blockscout/blockscout/pull/255) as an example PR that uses GitHub keywords and a Changelog to explain multiple changes. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..1e5bff0 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,22 @@ +*Describe your issue here.* + +### Environment + +* Deployment type (Manual/Docker/Docker-compose): +* Elixir & Erlang/OTP versions (`elixir -version`): +* Node JS version (`node -v`): +* Operating System: +* Blockscout Version/branch/commit: +* Archive node type && version (Erigon/Geth/Nethermind/Ganache/?): + +### Steps to reproduce + +*Tell us how to reproduce this issue. ❤ī¸ if you can push up a branch to your fork with a regression test we can run to reproduce locally.* + +### Expected behaviour + +*Tell us what should happen.* + +### Actual behaviour + +*Tell us what happens instead.* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..001deec --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +*[GitHub keywords to close any associated issues](https://blog.github.com/2013-05-14-closing-issues-via-pull-requests/)* + +## Motivation + +*Why we should merge these changes. If using GitHub keywords to close [issues](https://github.com/poanetwork/blockscout/issues), this is optional as the motivation can be read on the issue page.* + +## Changelog + +### Enhancements +*Things you added that don't break anything. Regression tests for Bug Fixes count as Enhancements.* + +### Bug Fixes +*Things you changed that fix bugs. If a fixes a bug, but in so doing adds a new requirement, removes code, or requires a database reset and reindex, the breaking part of the change should be added to Incompatible Changes below also.* + +### Incompatible Changes +*Things you broke while doing Enhancements and Bug Fixes. Breaking changes include (1) adding new requirements and (2) removing code. Renaming counts as (2) because a rename is a removal followed by an add.* + +## Upgrading + +*If you have any Incompatible Changes in the above Changelog, outline how users of prior versions can upgrade once this PR lands or when reviewers are testing locally. A common upgrading step is "Database reset and re-index required".* + +## Checklist for your Pull Request (PR) + + - [ ] I added an entry to `CHANGELOG.md` with this PR + - [ ] If I added new functionality, I added tests covering it. + - [ ] If I fixed a bug, I added a regression test to prevent the bug from silently reappearing again. + - [ ] I checked whether I should update the docs and did so by submitting a PR to https://github.com/blockscout/docs + - [ ] If I added/changed/removed ENV var, I submitted a PR to https://github.com/blockscout/docs to update the list of env vars at https://github.com/blockscout/docs/blob/master/for-developers/information-and-settings/env-variables.md and I updated the version to `master` in the Version column. Changes will be reflected in this table: https://docs.blockscout.com/for-developers/information-and-settings/env-variables. + - [ ] If I add new indices into DB, I checked, that they are not redundant with PGHero or other tools diff --git a/README.md b/README.md new file mode 100644 index 0000000..316e101 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +

BlockScout

+

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) +[![](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. + +For questions, comments and feature requests see the [discussions section](https://github.com/blockscout/blockscout/discussions). + +## About BlockScout + +BlockScout is an Elixir application that allows users to search transactions, view accounts and balances, and verify smart contracts on the Ethereum network including all forks and sidechains. + +Currently available full-featured block explorers (Etherscan, Etherchain, Blockchair) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent, open-source tools are needed to analyze and validate transactions. + +## Supported Projects + +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. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments. + +## License + +[![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + +This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details. diff --git a/apps/block_scout_web/.sobelow-conf b/apps/block_scout_web/.sobelow-conf new file mode 100644 index 0000000..99d6ca9 --- /dev/null +++ b/apps/block_scout_web/.sobelow-conf @@ -0,0 +1,12 @@ +[ + verbose: false, + private: true, + skip: true, + router: "lib/block_scout_web/router.ex", + exit: "low", + format: "compact", + ignore: ["Config.Headers", "Config.CSWH", "XSS.SendResp", "XSS.Raw"], + ignore_files: [ + "apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex" + ] +] diff --git a/apps/block_scout_web/API blueprint.md b/apps/block_scout_web/API blueprint.md new file mode 100644 index 0000000..9cbc20a --- /dev/null +++ b/apps/block_scout_web/API blueprint.md @@ -0,0 +1,2228 @@ +FORMAT: 1A +HOST:http://blockscout.com/xdai/testnet +# + + +# 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTBkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTEzQGJsb2Nrc2NvdXQuY29tZAACaWRh42QABG5hbWVtAAAAC1VzZXIgVGVzdDEwZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEwZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEwZAAMd2F0Y2hsaXN0X2lkYeM.d_nsIdBT4zP1sObizRp2ufpZ2-HDGFD1puY3eNSvftY; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gur6Ap5Rc1YAAAYC + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "nickname": "test_user10", + "name": "User Test10", + "email": "test_user-13@blockscout.com", + "avatar": "https://example.com/avatar/test_user10" + } +### 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMmQABWVtYWlsbQAAABp0ZXN0X3VzZXItMkBibG9ja3Njb3V0LmNvbWQAAmlkYdtkAARuYW1lbQAAAApVc2VyIFRlc3QyZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjJkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMmQADHdhdGNobGlzdF9pZGHb.XPfo6e6fTpCgSOVWcAgze_SHHkf_6UVp-SfOi2EVKcM; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gt7Hha-gjLUAABDh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "MyName", + "id": 65, + "address_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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMmQABWVtYWlsbQAAABp0ZXN0X3VzZXItMkBibG9ja3Njb3V0LmNvbWQAAmlkYdtkAARuYW1lbQAAAApVc2VyIFRlc3QyZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjJkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMmQADHdhdGNobGlzdF9pZGHb.XPfo6e6fTpCgSOVWcAgze_SHHkf_6UVp-SfOi2EVKcM; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gt8j_62gjLUAABFB + 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/72] +### BlockScoutWeb.Account.Api.V1.UserController update_tag_address [PUT /api/account/v1/user/tags/address/{id}] + + + + ++ Parameters + + id: `72` + id: 72 + + ++ Request Edit private address tag +**PUT**  `/api/account/v1/user/tags/address/72` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "name3", + "address_hash": "0x0000000000000000000000000000000000000054" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTdkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIxQGJsb2Nrc2NvdXQuY29tZAACaWRh6mQABG5hbWVtAAAAC1VzZXIgVGVzdDE3ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE3ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE3ZAAMd2F0Y2hsaXN0X2lkYeo.SwNPw9upySrwQX8GCp62J924WYWbJY-WNA31fMLjUas; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvKquVfUECUAAB4B + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "name3", + "id": 72, + "address_hash": "0x0000000000000000000000000000000000000054" + } +### 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE0QGJsb2Nrc2NvdXQuY29tZAACaWRh5GQABG5hbWVtAAAAC1VzZXIgVGVzdDExZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjExZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDExZAAMd2F0Y2hsaXN0X2lkYeQ.YOpB44xZNsuC9o5OZZQWpH-ijPijlYkT_fApVrfNuhs; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1guwn5VVeZtAAABdh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "name": "name2", + "id": 71, + "address_hash": "0x000000000000000000000000000000000000003a" + }, + { + "name": "name1", + "id": 70, + "address_hash": "0x0000000000000000000000000000000000000039" + }, + { + "name": "name0", + "id": 69, + "address_hash": "0x0000000000000000000000000000000000000038" + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_tag_address [DELETE /api/account/v1/user/tags/address/{id}] + + + + ++ Parameters + + id: `66` + id: 66 + + ++ Request Delete private address tag +**DELETE**  `/api/account/v1/user/tags/address/66` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNmQABWVtYWlsbQAAABp0ZXN0X3VzZXItN0BibG9ja3Njb3V0LmNvbWQAAmlkYd9kAARuYW1lbQAAAApVc2VyIFRlc3Q2ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjZkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwNmQADHdhdGNobGlzdF9pZGHf.2gy24vcTMAaovCIPA7q8PYmlv1ojuZGzgHCkQ6n_W70; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1guUM2L0cz9IAABXh + 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": "0x0000000000000000000000000000000000000000000000000000000000000009", + "name": "MyName" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI1QGJsb2Nrc2NvdXQuY29tZAACaWRh7mQABG5hbWVtAAAAC1VzZXIgVGVzdDIxZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIxZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIxZAAMd2F0Y2hsaXN0X2lkYe4.OALg_k0K4kFbxlwrk2_wILKz3Ojtx5g-lwqsQWUvTHE; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvV7jRTkLOwAACCB + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000009", + "name": "MyName", + "id": 72 + } + + ++ 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": "0x0000000000000000000000000000000000000000000000000000000000000008", + "name": "MyName" + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI1QGJsb2Nrc2NvdXQuY29tZAACaWRh7mQABG5hbWVtAAAAC1VzZXIgVGVzdDIxZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIxZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIxZAAMd2F0Y2hsaXN0X2lkYe4.OALg_k0K4kFbxlwrk2_wILKz3Ojtx5g-lwqsQWUvTHE; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvVV0ZPkLOwAACBh + 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/0x0000000000000000000000000000000000000000000000000000000000000009] +### BlockScoutWeb.Account.Api.V1.TagsController tags_transaction [GET /api/account/v1/tags/transaction/{transaction_hash}] + + + + ++ Parameters + + transaction_hash: `0x0000000000000000000000000000000000000000000000000000000000000009` + transaction_hash: 0x0000000000000000000000000000000000000000000000000000000000000009 + + ++ Request Get tags for transaction +**GET**  `/api/account/v1/tags/transaction/0x0000000000000000000000000000000000000000000000000000000000000009` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjFkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI1QGJsb2Nrc2NvdXQuY29tZAACaWRh7mQABG5hbWVtAAAAC1VzZXIgVGVzdDIxZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIxZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIxZAAMd2F0Y2hsaXN0X2lkYe4.OALg_k0K4kFbxlwrk2_wILKz3Ojtx5g-lwqsQWUvTHE; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvWZkx3kLOwAACCh + 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/65] +### BlockScoutWeb.Account.Api.V1.UserController update_tag_transaction [PUT /api/account/v1/user/tags/transaction/{id}] + + + + ++ Parameters + + id: `65` + id: 65 + + ++ Request Edit private transaction tag +**PUT**  `/api/account/v1/user/tags/transaction/65` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "name": "name1" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyOGQABWVtYWlsbQAAABp0ZXN0X3VzZXItOUBibG9ja3Njb3V0LmNvbWQAAmlkYeFkAARuYW1lbQAAAApVc2VyIFRlc3Q4ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjhkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwOGQADHdhdGNobGlzdF9pZGHh.CybEtb6DRCGrUsJ2qnEERIZwD6pRhUfUSwFugOLA9kg; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gunOuMiiGZsAAASi + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "name": "name1", + "id": 65 + } +### 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTRkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE4QGJsb2Nrc2NvdXQuY29tZAACaWRh52QABG5hbWVtAAAAC1VzZXIgVGVzdDE0ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE0ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE0ZAAMd2F0Y2hsaXN0X2lkYec.CDHGLjvSgiNStdl55exaXgWiuAWfGw65IX3_vK5h5dU; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gu9MDrtpGp0AABnh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000004", + "name": "name2", + "id": 68 + }, + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000003", + "name": "name1", + "id": 67 + }, + { + "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "name": "name0", + "id": 66 + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_tag_transaction [DELETE /api/account/v1/user/tags/transaction/{id}] + + + + ++ Parameters + + id: `69` + id: 69 + + ++ Request Delete private transaction tag +**DELETE**  `/api/account/v1/user/tags/transaction/69` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTZkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIwQGJsb2Nrc2NvdXQuY29tZAACaWRh6WQABG5hbWVtAAAAC1VzZXIgVGVzdDE2ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE2ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE2ZAAMd2F0Y2hsaXN0X2lkYek.LsY5H_7VsGeJ-WoDRIReTCTZmPTJNCTjme7ZshEuEpQ; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvGE13QyfYIAAByB + 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": true + }, + "name": "test16", + "address_hash": "0x0000000000000000000000000000000000000011" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyM2QABWVtYWlsbQAAABp0ZXN0X3VzZXItM0BibG9ja3Njb3V0LmNvbWQAAmlkYdxkAARuYW1lbQAAAApVc2VyIFRlc3QzZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjNkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwM2QADHdhdGNobGlzdF9pZGHc.ujumccFj98DtF6Rf_O0i31DGgry0eHmykzCC1xvjVfY; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gt-4UWemyBYAABJB + 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": true + }, + "name": "test16", + "id": 75, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000011", + "address_balance": null + } +### 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyM2QABWVtYWlsbQAAABp0ZXN0X3VzZXItM0BibG9ja3Njb3V0LmNvbWQAAmlkYdxkAARuYW1lbQAAAApVc2VyIFRlc3QzZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjNkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwM2QADHdhdGNobGlzdF9pZGHc.ujumccFj98DtF6Rf_O0i31DGgry0eHmykzCC1xvjVfY; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1guCYRuamyBYAAANj + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "notification_settings": { + "native": { + "outcoming": true, + "incoming": false + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": false, + "incoming": true + } + }, + "notification_methods": { + "email": false + }, + "name": "test17", + "id": 76, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000012", + "address_balance": null + }, + { + "notification_settings": { + "native": { + "outcoming": true, + "incoming": true + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": true + }, + "name": "test16", + "id": 75, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000011", + "address_balance": null + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_watchlist [DELETE /api/account/v1/user/watchlist/{id}] + + + + ++ Parameters + + id: `82` + id: 82 + + ++ Request Delete address from watchlist by id +**DELETE**  `/api/account/v1/user/watchlist/82` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTlkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIzQGJsb2Nrc2NvdXQuY29tZAACaWRh7GQABG5hbWVtAAAAC1VzZXIgVGVzdDE5ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE5ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE5ZAAMd2F0Y2hsaXN0X2lkYew.slyWFXgdvd78Pwp3lyrU5tmgCtF7VNIPHxnFkfAQ-YQ; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvR861_DWHcAAAhC + 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: `80` + id: 80 + + ++ Request Edit watchlist address +**PUT**  `/api/account/v1/user/watchlist/80` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": true, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": false + } + }, + "notification_methods": { + "email": false + }, + "name": "test21", + "address_hash": "0x0000000000000000000000000000000000000023" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyN2QABWVtYWlsbQAAABp0ZXN0X3VzZXItOEBibG9ja3Njb3V0LmNvbWQAAmlkYeBkAARuYW1lbQAAAApVc2VyIFRlc3Q3ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjdkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwN2QADHdhdGNobGlzdF9pZGHg.2IaE2naK_o4H_guVwcTb0JZIp2hs2c4fvtASxCmIWHM; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gugvkSj5PXEAAANi + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": true, + "incoming": true + }, + "ERC-20": { + "outcoming": false, + "incoming": false + } + }, + "notification_methods": { + "email": false + }, + "name": "test21", + "id": 80, + "exchange_rate": null, + "address_hash": "0x0000000000000000000000000000000000000023", + "address_balance": null + } +### 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": true, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": false + }, + "name": "test18", + "address_hash": "0x0000000000000000000000000000000000000013" + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNGQABWVtYWlsbQAAABp0ZXN0X3VzZXItNEBibG9ja3Njb3V0LmNvbWQAAmlkYd1kAARuYW1lbQAAAApVc2VyIFRlc3Q0ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjRkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwNGQADHdhdGNobGlzdF9pZGHd.jCNAb9dB6WGIZv9wIVL9tpikIPr056ChTYcDeSWdnG4; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1guGsUmFGrIUAABMB + 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: `79` + id: 79 + + ++ Request Example of error on editing watchlist address +**PUT**  `/api/account/v1/user/watchlist/79` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "notification_settings": { + "native": { + "outcoming": false, + "incoming": false + }, + "ERC-721": { + "outcoming": true, + "incoming": false + }, + "ERC-20": { + "outcoming": true, + "incoming": true + } + }, + "notification_methods": { + "email": false + }, + "name": "test18", + "address_hash": "0x0000000000000000000000000000000000000013" + } + ++ Response 422 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyNGQABWVtYWlsbQAAABp0ZXN0X3VzZXItNEBibG9ja3Njb3V0LmNvbWQAAmlkYd1kAARuYW1lbQAAAApVc2VyIFRlc3Q0ZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjRkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwNGQADHdhdGNobGlzdF9pZGHd.jCNAb9dB6WGIZv9wIVL9tpikIPr056ChTYcDeSWdnG4; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1guIKk8ZGrIUAABNB + 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjBkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI0QGJsb2Nrc2NvdXQuY29tZAACaWRh7WQABG5hbWVtAAAAC1VzZXIgVGVzdDIwZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIwZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIwZAAMd2F0Y2hsaXN0X2lkYe0.hIRgUayy_NKWZARAIxD2-TPy3PaP5kQSHuKGOLxxwz0; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvTjkbFZ2PwAACBB + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test", + "api_key": "05b65dfd-0d08-4aa1-b22b-95e3fc8a55e5" + } + + ++ 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTVkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE5QGJsb2Nrc2NvdXQuY29tZAACaWRh6GQABG5hbWVtAAAAC1VzZXIgVGVzdDE1ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE1ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE1ZAAMd2F0Y2hsaXN0X2lkYeg.M4suuaCnSncg5sgQepwyEGrDqMcSle2BvUjGq5qw0Q8; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gu_KXoEIU2IAABrh + 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTVkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE5QGJsb2Nrc2NvdXQuY29tZAACaWRh6GQABG5hbWVtAAAAC1VzZXIgVGVzdDE1ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE1ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE1ZAAMd2F0Y2hsaXN0X2lkYeg.M4suuaCnSncg5sgQepwyEGrDqMcSle2BvUjGq5qw0Q8; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gu_ZqjIIU2IAABsB + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "name": "test", + "api_key": "3d07da0e-428e-4410-bc54-43ab544e20f4" + }, + { + "name": "test", + "api_key": "92036fb5-a22a-418d-ac3a-0415e731d55a" + }, + { + "name": "test", + "api_key": "0262ffe5-6d6a-4f79-8444-479e8be85d0e" + } + ] +### BlockScoutWeb.Account.Api.V1.UserController update_api_key [PUT /api/account/v1/user/api_keys/{api_key}] + + + + ++ Parameters + + api_key: `6bcec727-d945-4785-99b6-c6094bbf0452` + api_key: 6bcec727-d945-4785-99b6-c6094bbf0452 + + ++ Request Edit api key +**PUT**  `/api/account/v1/user/api_keys/6bcec727-d945-4785-99b6-c6094bbf0452` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test_1" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMGQABWVtYWlsbQAAABp0ZXN0X3VzZXItMEBibG9ja3Njb3V0LmNvbWQAAmlkYdlkAARuYW1lbQAAAApVc2VyIFRlc3QwZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjBkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMGQADHdhdGNobGlzdF9pZGHZ.eNhiwGmTdeNAVqQGfVgtac9gGTsoXnysChIBQN75BQk; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gtunEs8BJMYAABCE + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test_1", + "api_key": "6bcec727-d945-4785-99b6-c6094bbf0452" + } +### BlockScoutWeb.Account.Api.V1.UserController delete_api_key [DELETE /api/account/v1/user/api_keys/{api_key}] + + + + ++ Parameters + + api_key: `0e26955f-5431-4652-84da-d08aded97a28` + api_key: 0e26955f-5431-4652-84da-d08aded97a28 + + ++ Request Delete api key +**DELETE**  `/api/account/v1/user/api_keys/0e26955f-5431-4652-84da-d08aded97a28` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMThkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTIyQGJsb2Nrc2NvdXQuY29tZAACaWRh62QABG5hbWVtAAAAC1VzZXIgVGVzdDE4ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjE4ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDE4ZAAMd2F0Y2hsaXN0X2lkYes.NYp71-Be73f-HTquq2QWWCa70c169Rd9GXDOOSCdC34; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvMpP3rEvHcAAAei + 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": "test26", + "contract_address_hash": "0x0000000000000000000000000000000000000089", + "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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjNkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTM3QGJsb2Nrc2NvdXQuY29tZAACaWRh8GQABG5hbWVtAAAAC1VzZXIgVGVzdDIzZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIzZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIzZAAMd2F0Y2hsaXN0X2lkYfA.EgDkDw8R9zBMVjqsTcEWr77klYQVx6QOCcxXyN7EAqg; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvk62Sj0d-gAAArC + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test26", + "id": 161, + "contract_address_hash": "0x0000000000000000000000000000000000000089", + "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": "test15", + "contract_address_hash": "0x0000000000000000000000000000000000000010", + "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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMWQABWVtYWlsbQAAABp0ZXN0X3VzZXItMUBibG9ja3Njb3V0LmNvbWQAAmlkYdpkAARuYW1lbQAAAApVc2VyIFRlc3QxZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjFkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMWQADHdhdGNobGlzdF9pZGHa.ynGrz6gad7RIkTh1lopco9xXNhiI-y6Bm6ecAnv3Usg; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gt5BIL0fpssAABCB + 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMWQABWVtYWlsbQAAABp0ZXN0X3VzZXItMUBibG9ja3Njb3V0LmNvbWQAAmlkYdpkAARuYW1lbQAAAApVc2VyIFRlc3QxZAAIbmlja25hbWVtAAAACnRlc3RfdXNlcjFkAAN1aWRtAAAAD2Jsb2Nrc2NvdXR8MDAwMWQADHdhdGNobGlzdF9pZGHa.ynGrz6gad7RIkTh1lopco9xXNhiI-y6Bm6ecAnv3Usg; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gt5U3pwfpssAABCh + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "name": "test14", + "id": 159, + "contract_address_hash": "0x000000000000000000000000000000000000000f", + "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": 158, + "contract_address_hash": "0x000000000000000000000000000000000000000e", + "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": 157, + "contract_address_hash": "0x000000000000000000000000000000000000000d", + "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": 156, + "contract_address_hash": "0x000000000000000000000000000000000000000c", + "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": 155, + "contract_address_hash": "0x000000000000000000000000000000000000000b", + "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": 154, + "contract_address_hash": "0x000000000000000000000000000000000000000a", + "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": 153, + "contract_address_hash": "0x0000000000000000000000000000000000000009", + "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": 152, + "contract_address_hash": "0x0000000000000000000000000000000000000008", + "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": 151, + "contract_address_hash": "0x0000000000000000000000000000000000000007", + "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": 150, + "contract_address_hash": "0x0000000000000000000000000000000000000006", + "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": 149, + "contract_address_hash": "0x0000000000000000000000000000000000000005", + "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": "test3", + "id": 148, + "contract_address_hash": "0x0000000000000000000000000000000000000004", + "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": "test2", + "id": 147, + "contract_address_hash": "0x0000000000000000000000000000000000000003", + "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": "test1", + "id": 146, + "contract_address_hash": "0x0000000000000000000000000000000000000002", + "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": "test0", + "id": 145, + "contract_address_hash": "0x0000000000000000000000000000000000000001", + "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: `160` + id: 160 + + ++ Request Edit custom abi +**PUT**  `/api/account/v1/user/custom_abis/160` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "name": "test23", + "contract_address_hash": "0x0000000000000000000000000000000000000046", + "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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTNkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE3QGJsb2Nrc2NvdXQuY29tZAACaWRh5mQABG5hbWVtAAAAC1VzZXIgVGVzdDEzZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEzZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEzZAAMd2F0Y2hsaXN0X2lkYeY.sl0nMtxBkMGt3aK7ohM3AYMcNEI-l37Xvqvl9qZ2Tso; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gu0y0bFQlB0AAAbi + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "name": "test23", + "id": 160, + "contract_address_hash": "0x0000000000000000000000000000000000000046", + "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: `162` + id: 162 + + ++ Request Delete custom abi +**DELETE**  `/api/account/v1/user/custom_abis/162` + + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjRkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTM4QGJsb2Nrc2NvdXQuY29tZAACaWRh8WQABG5hbWVtAAAAC1VzZXIgVGVzdDI0ZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjI0ZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDI0ZAAMd2F0Y2hsaXN0X2lkYfE.i0XOrEfBULTfd08Ig4nhy_veB1sWxl2UWYT9kkveABw; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvnkpEhLN3QAACMB + 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": "website3", + "tags": "Tag5;Tag6", + "is_owner": false, + "full_name": "full name3", + "email": "test_user-16@blockscout.com", + "company": "company3", + "addresses": [ + "0x000000000000000000000000000000000000003b", + "0x000000000000000000000000000000000000003c", + "0x000000000000000000000000000000000000003d", + "0x000000000000000000000000000000000000003e", + "0x000000000000000000000000000000000000003f", + "0x0000000000000000000000000000000000000040", + "0x0000000000000000000000000000000000000041", + "0x0000000000000000000000000000000000000042", + "0x0000000000000000000000000000000000000043", + "0x0000000000000000000000000000000000000044" + ], + "additional_comment": "additional_comment3" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMTJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTE1QGJsb2Nrc2NvdXQuY29tZAACaWRh5WQABG5hbWVtAAAAC1VzZXIgVGVzdDEyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjEyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDEyZAAMd2F0Y2hsaXN0X2lkYeU.8B0VERlCeTBlp1w0Zys_ZGaVIKj0VYi6pV2wMnCjeac; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1guxmyw_F-rUAAATj + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "website": "website3", + "tags": "Tag5;Tag6", + "submission_date": "2022-09-03T21:02:22.651943Z", + "is_owner": false, + "id": 146, + "full_name": "full name3", + "email": "test_user-16@blockscout.com", + "company": "company3", + "addresses": [ + "0x000000000000000000000000000000000000003b", + "0x000000000000000000000000000000000000003c", + "0x000000000000000000000000000000000000003d", + "0x000000000000000000000000000000000000003e", + "0x000000000000000000000000000000000000003f", + "0x0000000000000000000000000000000000000040", + "0x0000000000000000000000000000000000000041", + "0x0000000000000000000000000000000000000042", + "0x0000000000000000000000000000000000000043", + "0x0000000000000000000000000000000000000044" + ], + "additional_comment": "additional_comment3" + } +### 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.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI2QGJsb2Nrc2NvdXQuY29tZAACaWRh72QABG5hbWVtAAAAC1VzZXIgVGVzdDIyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIyZAAMd2F0Y2hsaXN0X2lkYe8.oZY96LW6ZLfw1aK-C5TYkrK2GRNQEJCapnUSkd5OjXU; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvdQvQ8r6iIAAAki + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + [ + { + "website": "website13", + "tags": "Tag18;Tag19", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 156, + "full_name": "full name13", + "email": "test_user-36@blockscout.com", + "company": "company13", + "addresses": [ + "0x0000000000000000000000000000000000000084", + "0x0000000000000000000000000000000000000085", + "0x0000000000000000000000000000000000000086", + "0x0000000000000000000000000000000000000087", + "0x0000000000000000000000000000000000000088" + ], + "additional_comment": "additional_comment13" + }, + { + "website": "website12", + "tags": "Tag17", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": true, + "id": 155, + "full_name": "full name12", + "email": "test_user-35@blockscout.com", + "company": "company12", + "addresses": [ + "0x0000000000000000000000000000000000000083" + ], + "additional_comment": "additional_comment12" + }, + { + "website": "website11", + "tags": "Tag16", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 154, + "full_name": "full name11", + "email": "test_user-34@blockscout.com", + "company": "company11", + "addresses": [ + "0x000000000000000000000000000000000000007b", + "0x000000000000000000000000000000000000007c", + "0x000000000000000000000000000000000000007d", + "0x000000000000000000000000000000000000007e", + "0x000000000000000000000000000000000000007f", + "0x0000000000000000000000000000000000000080", + "0x0000000000000000000000000000000000000081", + "0x0000000000000000000000000000000000000082" + ], + "additional_comment": "additional_comment11" + }, + { + "website": "website10", + "tags": "Tag15", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 153, + "full_name": "full name10", + "email": "test_user-33@blockscout.com", + "company": "company10", + "addresses": [ + "0x0000000000000000000000000000000000000073", + "0x0000000000000000000000000000000000000074", + "0x0000000000000000000000000000000000000075", + "0x0000000000000000000000000000000000000076", + "0x0000000000000000000000000000000000000077", + "0x0000000000000000000000000000000000000078", + "0x0000000000000000000000000000000000000079", + "0x000000000000000000000000000000000000007a" + ], + "additional_comment": "additional_comment10" + }, + { + "website": "website9", + "tags": "Tag14", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 152, + "full_name": "full name9", + "email": "test_user-32@blockscout.com", + "company": "company9", + "addresses": [ + "0x000000000000000000000000000000000000006d", + "0x000000000000000000000000000000000000006e", + "0x000000000000000000000000000000000000006f", + "0x0000000000000000000000000000000000000070", + "0x0000000000000000000000000000000000000071", + "0x0000000000000000000000000000000000000072" + ], + "additional_comment": "additional_comment9" + }, + { + "website": "website8", + "tags": "Tag13", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 151, + "full_name": "full name8", + "email": "test_user-31@blockscout.com", + "company": "company8", + "addresses": [ + "0x0000000000000000000000000000000000000064", + "0x0000000000000000000000000000000000000065", + "0x0000000000000000000000000000000000000066", + "0x0000000000000000000000000000000000000067", + "0x0000000000000000000000000000000000000068", + "0x0000000000000000000000000000000000000069", + "0x000000000000000000000000000000000000006a", + "0x000000000000000000000000000000000000006b", + "0x000000000000000000000000000000000000006c" + ], + "additional_comment": "additional_comment8" + }, + { + "website": "website7", + "tags": "Tag11;Tag12", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": true, + "id": 150, + "full_name": "full name7", + "email": "test_user-30@blockscout.com", + "company": "company7", + "addresses": [ + "0x0000000000000000000000000000000000000063" + ], + "additional_comment": "additional_comment7" + }, + { + "website": "website6", + "tags": "Tag9;Tag10", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 149, + "full_name": "full name6", + "email": "test_user-29@blockscout.com", + "company": "company6", + "addresses": [ + "0x0000000000000000000000000000000000000060", + "0x0000000000000000000000000000000000000061", + "0x0000000000000000000000000000000000000062" + ], + "additional_comment": "additional_comment6" + }, + { + "website": "website5", + "tags": "Tag8", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": true, + "id": 148, + "full_name": "full name5", + "email": "test_user-28@blockscout.com", + "company": "company5", + "addresses": [ + "0x000000000000000000000000000000000000005e", + "0x000000000000000000000000000000000000005f" + ], + "additional_comment": "additional_comment5" + }, + { + "website": "website4", + "tags": "Tag7", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 147, + "full_name": "full name4", + "email": "test_user-27@blockscout.com", + "company": "company4", + "addresses": [ + "0x000000000000000000000000000000000000005b", + "0x000000000000000000000000000000000000005c", + "0x000000000000000000000000000000000000005d" + ], + "additional_comment": "additional_comment4" + } + ] +### BlockScoutWeb.Account.Api.V1.UserController delete_public_tags_request [DELETE /api/account/v1/user/public_tags/{id}] + + + + ++ Parameters + + id: `156` + id: 156 + + ++ Request Delete public tags request +**DELETE**  `/api/account/v1/user/public_tags/156` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "remove_reason": "reason" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAmaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyMjJkAAVlbWFpbG0AAAAbdGVzdF91c2VyLTI2QGJsb2Nrc2NvdXQuY29tZAACaWRh72QABG5hbWVtAAAAC1VzZXIgVGVzdDIyZAAIbmlja25hbWVtAAAAC3Rlc3RfdXNlcjIyZAADdWlkbQAAABBibG9ja3Njb3V0fDAwMDIyZAAMd2F0Y2hsaXN0X2lkYe8.oZY96LW6ZLfw1aK-C5TYkrK2GRNQEJCapnUSkd5OjXU; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1gvdm8H0r6iIAAAlC + 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: `145` + id: 145 + + ++ Request Edit request to add a public tag +**PUT**  `/api/account/v1/user/public_tags/145` + + + Headers + + content-type: multipart/mixed; boundary=plug_conn_test + + Body + + { + "website": "website2", + "tags": "Tag3;Tag4", + "is_owner": false, + "full_name": "full name2", + "email": "test_user-12@blockscout.com", + "company": "company2", + "addresses": [ + "0x000000000000000000000000000000000000002f", + "0x0000000000000000000000000000000000000030", + "0x0000000000000000000000000000000000000031", + "0x0000000000000000000000000000000000000032", + "0x0000000000000000000000000000000000000033", + "0x0000000000000000000000000000000000000034", + "0x0000000000000000000000000000000000000035", + "0x0000000000000000000000000000000000000036", + "0x0000000000000000000000000000000000000037" + ], + "additional_comment": "additional_comment2" + } + ++ Response 200 + + + Headers + + set-cookie: _explorer_key=SFMyNTY.g3QAAAABbQAAAAxjdXJyZW50X3VzZXJ0AAAAB2QABmF2YXRhcm0AAAAlaHR0cHM6Ly9leGFtcGxlLmNvbS9hdmF0YXIvdGVzdF91c2VyOWQABWVtYWlsbQAAABt0ZXN0X3VzZXItMTBAYmxvY2tzY291dC5jb21kAAJpZGHiZAAEbmFtZW0AAAAKVXNlciBUZXN0OWQACG5pY2tuYW1lbQAAAAp0ZXN0X3VzZXI5ZAADdWlkbQAAAA9ibG9ja3Njb3V0fDAwMDlkAAx3YXRjaGxpc3RfaWRh4g.cM2caeO_bvTyojrTAKD7Tt4WEPEIsHwTMmWkTEVgSLo; path=/; HttpOnly + content-type: application/json; charset=utf-8 + cache-control: max-age=0, private, must-revalidate + x-request-id: FxF1guqVaODqqc8AAAUi + access-control-allow-credentials: true + access-control-allow-origin: * + access-control-expose-headers: + + Body + + { + "website": "website2", + "tags": "Tag3;Tag4", + "submission_date": "2022-09-03T21:02:23.000000Z", + "is_owner": false, + "id": 145, + "full_name": "full name2", + "email": "test_user-12@blockscout.com", + "company": "company2", + "addresses": [ + "0x000000000000000000000000000000000000002f", + "0x0000000000000000000000000000000000000030", + "0x0000000000000000000000000000000000000031", + "0x0000000000000000000000000000000000000032", + "0x0000000000000000000000000000000000000033", + "0x0000000000000000000000000000000000000034", + "0x0000000000000000000000000000000000000035", + "0x0000000000000000000000000000000000000036", + "0x0000000000000000000000000000000000000037" + ], + "additional_comment": "additional_comment2" + } + diff --git a/apps/block_scout_web/API.md b/apps/block_scout_web/API.md new file mode 100644 index 0000000..afc68c1 --- /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 new file mode 100644 index 0000000..8c1a622 --- /dev/null +++ b/apps/block_scout_web/README.md @@ -0,0 +1,43 @@ +# BlockScout Web + +This is a tool for inspecting and analyzing the POA Network blockchain from a web browser. + +## Machine Requirements + +* Erlang/OTP 21+ +* 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.) + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +You can also run IEx (Interactive Elixir): `$ iex -S mix phx.server` (This can be run from this directory or the project root: the project root is recommended.) + +### 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` + +## Internationalization + +The app is currently internationalized. It is only localized to U.S. English. + +To translate new strings, run `$ mix gettext.extract --merge` and edit the new strings in `priv/gettext/en/LC_MESSAGES/default.po`. diff --git a/apps/block_scout_web/assets/.babelrc b/apps/block_scout_web/assets/.babelrc new file mode 100644 index 0000000..db967cb --- /dev/null +++ b/apps/block_scout_web/assets/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@babel/preset-env", { "useBuiltIns": "usage", "corejs": { "version": 3 } }]] +} diff --git a/apps/block_scout_web/assets/.eslintrc b/apps/block_scout_web/assets/.eslintrc new file mode 100644 index 0000000..535509b --- /dev/null +++ b/apps/block_scout_web/assets/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": "standard", + "env": { + "browser": true + } +} diff --git a/apps/block_scout_web/assets/README.md b/apps/block_scout_web/assets/README.md new file mode 100644 index 0000000..c4e098c --- /dev/null +++ b/apps/block_scout_web/assets/README.md @@ -0,0 +1,9 @@ +# Javascript structure files + +## lib + +* This folder is used to place `component` files, that may span in multiple pages. + +## pages + +* This folder is used to place `page` specific files, that won't be reusable in other locations. diff --git a/apps/block_scout_web/assets/__mocks__/css/app.scss.js b/apps/block_scout_web/assets/__mocks__/css/app.scss.js new file mode 100644 index 0000000..1a86a4f --- /dev/null +++ b/apps/block_scout_web/assets/__mocks__/css/app.scss.js @@ -0,0 +1,4 @@ +export default { + primary: "#4786ff", + secondary: "#ced4da" +} diff --git a/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js b/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js new file mode 100644 index 0000000..d6d187c --- /dev/null +++ b/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js @@ -0,0 +1,82 @@ +/** + * @jest-environment jsdom + */ + +import { asyncReducer, asyncInitialState } from '../../js/lib/async_listing_load' + +describe('ELEMENTS_LOAD', () => { + test('sets only nextPagePath and ignores other keys', () => { + const state = Object.assign({}, asyncInitialState) + const action = { type: 'ELEMENTS_LOAD', nextPagePath: 'set', foo: 1 } + const output = asyncReducer(state, action) + + expect(output.foo).not.toEqual(1) + expect(output.nextPagePath).toEqual('set') + }) +}) + +describe('ADD_ITEM_KEY', () => { + test('sets itemKey to what was passed in the action', () => { + const expectedItemKey = 'expected.Key' + + const state = Object.assign({}, asyncInitialState) + const action = { type: 'ADD_ITEM_KEY', itemKey: expectedItemKey } + const output = asyncReducer(state, action) + + expect(output.itemKey).toEqual(expectedItemKey) + }) +}) + +describe('START_REQUEST', () => { + test('sets loading status to true', () => { + const state = Object.assign({}, asyncInitialState, { loading: false }) + const action = { type: 'START_REQUEST' } + const output = asyncReducer(state, action) + + expect(output.loading).toEqual(true) + }) +}) + +describe('REQUEST_ERROR', () => { + test('sets requestError to true', () => { + const state = Object.assign({}, asyncInitialState, { requestError: false }) + const action = { type: 'REQUEST_ERROR' } + const output = asyncReducer(state, action) + + expect(output.requestError).toEqual(true) + }) +}) + +describe('FINISH_REQUEST', () => { + test('sets loading status to false', () => { + const state = Object.assign({}, asyncInitialState, { + loading: true + }) + const action = { type: 'FINISH_REQUEST' } + const output = asyncReducer(state, action) + + expect(output.loading).toEqual(false) + }) +}) + +describe('ITEMS_FETCHED', () => { + test('sets the items to what was passed in the action', () => { + const expectedItems = [1, 2, 3] + + const state = Object.assign({}, asyncInitialState) + const action = { type: 'ITEMS_FETCHED', items: expectedItems } + const output = asyncReducer(state, action) + + expect(output.items).toEqual(expectedItems) + }) +}) + +describe('NAVIGATE_TO_OLDER', () => { + test('sets beyondPageOne to true', () => { + const state = Object.assign({}, asyncInitialState, { beyondPageOne: false }) + const action = { type: 'NAVIGATE_TO_OLDER' } + const output = asyncReducer(state, action) + + expect(output.beyondPageOne).toEqual(true) + }) +}) diff --git a/apps/block_scout_web/assets/__tests__/lib/autocomplete.js b/apps/block_scout_web/assets/__tests__/lib/autocomplete.js new file mode 100644 index 0000000..877474c --- /dev/null +++ b/apps/block_scout_web/assets/__tests__/lib/autocomplete.js @@ -0,0 +1,31 @@ +/** + * @jest-environment jsdom + */ + + import { searchEngine } from '../../js/lib/autocomplete' + + test('searchEngine', () => { + expect(searchEngine('qwe', { + 'name': 'Test', + 'symbol': 'TST', + 'address_hash': '0x000', + 'tx_hash': '0x000', + 'block_hash': '0x000' + })).toEqual(undefined) + + expect(searchEngine('tes', { + 'name': 'Test', + 'symbol': 'TST', + 'address_hash': '0x000', + 'tx_hash': '0x000', + 'block_hash': '0x000' + })).toEqual('
0x000
Test (TST)
') + + expect(searchEngine('qwe', { + 'name': 'qwe1\'">')) + $('#csv-iframe').attr('src', downloadUrl) + + const interval = setInterval(handleCSVDownloaded, 1000) + setTimeout(resetDownload, 60000) + + function handleCSVDownloaded () { + if (Cookies.get('csv-downloaded') === 'true') { + resetDownload() + } + } + + function resetDownload () { + $button.removeClass('spinner') + $button.prop('disabled', false) + clearInterval(interval) + Cookies.remove('csv-downloaded') + // eslint-disable-next-line + grecaptcha.reset() + } + } +}) + +function onSelect (date, paramToReplace) { + const formattedDate = moment(date).format(DATE_FORMAT) + + if (date) { + const csvExportPath = $button.data('link') + + const updatedCsvExportUrl = replaceUrlParam(csvExportPath, paramToReplace, formattedDate) + $button.data('link', updatedCsvExportUrl) + } +} + +function replaceUrlParam (url, paramName, paramValue) { + if (paramValue == null) { + paramValue = '' + } + const pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)') + if (url.search(pattern) >= 0) { + return url.replace(pattern, '$1' + paramValue + '$2') + } + url = url.replace(/[?#]$/, '') + return url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue +} diff --git a/apps/block_scout_web/assets/js/lib/currency.js b/apps/block_scout_web/assets/js/lib/currency.js new file mode 100644 index 0000000..02dd537 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/currency.js @@ -0,0 +1,72 @@ +import $ from 'jquery' +import numeral from 'numeral' +import { BigNumber } from 'bignumber.js' + +export function formatUsdValue (value) { + const formattedValue = formatCurrencyValue(value) + if (formattedValue === 'N/A') { + return formattedValue + } else { + return `${formattedValue} USD` + } +} + +function formatTokenUsdValue (value) { + return formatCurrencyValue(value, '@') +} + +function formatCurrencyValue (value, symbol) { + symbol = symbol || '$' + if (isNaN(value)) return 'N/A' + if (value === 0 || value === '0') return `${symbol}0.00` + if (value < 0.000001) return `${window.localized['Less than']} ${symbol}0.000001` + if (value < 1) return `${symbol}${numeral(value).format('0.000000')}` + if (value < 100000) return `${symbol}${numeral(value).format('0,0.00')}` + if (value > 1000000000000) return `${symbol}${numeral(value).format('0.000e+0')}` + return `${symbol}${numeral(value).format('0,0')}` +} + +function weiToEther (wei) { + return new BigNumber(wei).dividedBy('1000000000000000000').toNumber() +} + +function etherToUSD (ether, usdExchangeRate) { + return new BigNumber(ether).multipliedBy(usdExchangeRate).toNumber() +} + +export function formatAllUsdValues (root) { + root = root || $(':root') + + root.find('[data-usd-value]').each((i, el) => { + el.innerHTML = formatUsdValue(el.dataset.usdValue) + }) + root.find('[data-token-usd-value]').each((i, el) => { + el.innerHTML = formatTokenUsdValue(el.dataset.tokenUsdValue) + }) + + return root +} +formatAllUsdValues() + +function tryUpdateCalculatedUsdValues (el, usdExchangeRate = el.dataset.usdExchangeRate) { + // eslint-disable-next-line no-prototype-builtins + if (!el.dataset.hasOwnProperty('weiValue')) return + const ether = weiToEther(el.dataset.weiValue) + const usd = etherToUSD(ether, usdExchangeRate) + const formattedUsd = formatUsdValue(usd) + if (formattedUsd !== el.innerHTML) { + $(el).data('rawUsdValue', usd) + el.innerHTML = formattedUsd + } +} + +function tryUpdateUnitPriceValues (el, usdUnitPrice = el.dataset.usdUnitPrice) { + const formattedValue = formatCurrencyValue(usdUnitPrice) + if (formattedValue !== el.innerHTML) el.innerHTML = formattedValue +} + +export function updateAllCalculatedUsdValues (usdExchangeRate) { + $('[data-usd-exchange-rate]').each((i, el) => tryUpdateCalculatedUsdValues(el, usdExchangeRate)) + $('[data-usd-unit-price]').each((i, el) => tryUpdateUnitPriceValues(el, usdExchangeRate)) +} +updateAllCalculatedUsdValues() diff --git a/apps/block_scout_web/assets/js/lib/custom_ad.json b/apps/block_scout_web/assets/js/lib/custom_ad.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/custom_ad.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/apps/block_scout_web/assets/js/lib/dropzone.js b/apps/block_scout_web/assets/js/lib/dropzone.js new file mode 100644 index 0000000..645c4dd --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/dropzone.js @@ -0,0 +1 @@ +import 'dropzone' diff --git a/apps/block_scout_web/assets/js/lib/from_now.js b/apps/block_scout_web/assets/js/lib/from_now.js new file mode 100644 index 0000000..32dc90b --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/from_now.js @@ -0,0 +1,35 @@ +import $ from 'jquery' +import moment from 'moment' + +moment.relativeTimeThreshold('M', 12) +moment.relativeTimeThreshold('d', 30) +moment.relativeTimeThreshold('h', 24) +moment.relativeTimeThreshold('m', 60) +moment.relativeTimeThreshold('s', 60) +moment.relativeTimeThreshold('ss', 1) + +export function updateAllAges ($container = $(document)) { + $container.find('[data-from-now]').each((i, el) => tryUpdateAge(el)) + return $container +} +function tryUpdateAge (el) { + if (!el.dataset.fromNow) return + + const timestamp = moment(el.dataset.fromNow) + if (timestamp.isValid()) updateAge(el, timestamp) +} +function updateAge (el, timestamp) { + let fromNow = timestamp.fromNow() + // show the exact time only for transaction details page. Otherwise, short entry + const elInTile = el.hasAttribute('in-tile') + if ((window.location.pathname.includes('/tx/') || window.location.pathname.includes('/block/') || window.location.pathname.includes('/blocks/')) && !elInTile) { + const offset = moment().utcOffset() / 60 + const sign = offset && Math.sign(offset) ? '+' : '-' + const formatDate = `MMMM-DD-YYYY hh:mm:ss A ${sign}${offset} UTC` + fromNow = `${fromNow} | ${timestamp.format(formatDate)}` + } + if (fromNow !== el.innerHTML) el.innerHTML = fromNow +} +updateAllAges() + +setInterval(updateAllAges, 1000) diff --git a/apps/block_scout_web/assets/js/lib/history_chart.js b/apps/block_scout_web/assets/js/lib/history_chart.js new file mode 100644 index 0000000..c7c94cb --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/history_chart.js @@ -0,0 +1,342 @@ +import $ from 'jquery' +import { Chart, LineController, LineElement, PointElement, LinearScale, TimeScale, Title, Tooltip } from 'chart.js' +import 'chartjs-adapter-luxon' +import humps from 'humps' +import numeral from 'numeral' +import { DateTime } from 'luxon' +import { formatUsdValue } from '../lib/currency' +import sassVariables from '../../css/export-vars-to-js.module.scss' + +Chart.defaults.font.family = 'Nunito, "Helvetica Neue", Arial, sans-serif,"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"' +Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale, Title, Tooltip) + +const grid = { + display: false, + drawBorder: false, + drawOnChartArea: false +} + +function getTxChartColor () { + if (localStorage.getItem('current-color-mode') === 'dark') { + return sassVariables.dashboardLineColorTransactionsDarkTheme + } else { + return sassVariables.dashboardLineColorTransactions + } +} + +function getPriceChartColor () { + if (localStorage.getItem('current-color-mode') === 'dark') { + return sassVariables.dashboardLineColorPriceDarkTheme + } else { + return sassVariables.dashboardLineColorPrice + } +} + +function getMarketCapChartColor () { + if (localStorage.getItem('current-color-mode') === 'dark') { + return sassVariables.dashboardLineColorMarketDarkTheme + } else { + return sassVariables.dashboardLineColorMarket + } +} + +function xAxe (fontColor) { + return { + grid, + type: 'time', + time: { + unit: 'day', + tooltipFormat: 'DD', + stepSize: 14 + }, + ticks: { + color: fontColor + } + } +} + +const padding = { + left: 20, + right: 20 +} + +const legend = { + display: false +} + +function formatValue (val) { + return `${numeral(val).format('0,0')}` +} + +const config = { + type: 'line', + responsive: true, + data: { + datasets: [] + }, + options: { + layout: { + padding + }, + interaction: { + intersect: false, + mode: 'index' + }, + scales: { + x: xAxe(sassVariables.dashboardBannerChartAxisFontColor), + price: { + position: 'left', + grid, + ticks: { + beginAtZero: true, + callback: (value, _index, _values) => `$${numeral(value).format('0,0.00')}`, + maxTicksLimit: 4, + color: sassVariables.dashboardBannerChartAxisFontColor + } + }, + marketCap: { + position: 'right', + grid, + ticks: { + callback: (_value, _index, _values) => '', + maxTicksLimit: 6, + drawOnChartArea: false, + color: sassVariables.dashboardBannerChartAxisFontColor + } + }, + numTransactions: { + position: 'right', + grid, + ticks: { + beginAtZero: true, + callback: (value, _index, _values) => formatValue(value), + maxTicksLimit: 4, + color: sassVariables.dashboardBannerChartAxisFontColor + } + } + }, + plugins: { + legend, + title: { + display: true, + color: sassVariables.dashboardBannerChartAxisFontColor + }, + tooltip: { + mode: 'index', + intersect: false, + callbacks: { + label: (context) => { + const { label } = context.dataset + const { formattedValue, parsed } = context + if (context.dataset.yAxisID === 'price') { + return `${label}: ${formatUsdValue(parsed.y)}` + } else if (context.dataset.yAxisID === 'marketCap') { + return `${label}: ${formatUsdValue(parsed.y)}` + } else if (context.dataset.yAxisID === 'numTransactions') { + return `${label}: ${formattedValue}` + } else { + return formattedValue + } + } + } + } + } + } +} + +function getDataFromLocalStorage (key) { + const data = window.localStorage.getItem(key) + return data ? JSON.parse(data) : [] +} + +function setDataToLocalStorage (key, data) { + window.localStorage.setItem(key, JSON.stringify(data)) +} + +function getPriceData (marketHistoryData) { + if (marketHistoryData.length === 0) { + return getDataFromLocalStorage('priceData') + } + const data = marketHistoryData.map(({ date, closingPrice }) => ({ x: date, y: closingPrice })) + setDataToLocalStorage('priceData', data) + return data +} + +function getTxHistoryData (transactionHistory) { + if (transactionHistory.length === 0) { + return getDataFromLocalStorage('txHistoryData') + } + const data = transactionHistory.map(dataPoint => ({ x: dataPoint.date, y: dataPoint.number_of_transactions })) + + // it should be empty value for tx history the current day + const prevDayStr = data[0].x + const prevDay = DateTime.fromISO(prevDayStr) + let curDay = prevDay.plus({ days: 1 }) + curDay = curDay.toISODate() + data.unshift({ x: curDay, y: null }) + + setDataToLocalStorage('txHistoryData', data) + return data +} + +function getMarketCapData (marketHistoryData, availableSupply) { + if (marketHistoryData.length === 0) { + return getDataFromLocalStorage('marketCapData') + } + const data = marketHistoryData.map(({ date, closingPrice }) => { + const supply = (availableSupply !== null && typeof availableSupply === 'object') + ? availableSupply[date] + : availableSupply + return { x: date, y: closingPrice * supply } + }) + setDataToLocalStorage('marketCapData', data) + return data +} + +// colors for light and dark theme +const priceLineColor = getPriceChartColor() +const mcapLineColor = getMarketCapChartColor() + +class MarketHistoryChart { + constructor (el, availableSupply, _marketHistoryData, dataConfig) { + const axes = config.options.scales + + let priceActivated = true + let marketCapActivated = true + + this.price = { + label: window.localized.Price, + yAxisID: 'price', + data: [], + fill: false, + cubicInterpolationMode: 'monotone', + pointRadius: 0, + backgroundColor: priceLineColor, + borderColor: priceLineColor + // lineTension: 0 + } + if (dataConfig.market === undefined || dataConfig.market.indexOf('price') === -1) { + this.price.hidden = true + axes.price.display = false + priceActivated = false + } + + this.marketCap = { + label: window.localized['Market Cap'], + yAxisID: 'marketCap', + data: [], + fill: false, + cubicInterpolationMode: 'monotone', + pointRadius: 0, + backgroundColor: mcapLineColor, + borderColor: mcapLineColor + // lineTension: 0 + } + if (dataConfig.market === undefined || dataConfig.market.indexOf('market_cap') === -1) { + this.marketCap.hidden = true + axes.marketCap.display = false + this.price.hidden = true + axes.price.display = false + marketCapActivated = false + } + + this.numTransactions = { + label: window.localized['Tx/day'], + yAxisID: 'numTransactions', + data: [], + cubicInterpolationMode: 'monotone', + fill: false, + pointRadius: 0, + backgroundColor: getTxChartColor(), + borderColor: getTxChartColor() + // lineTension: 0 + } + + if (dataConfig.transactions === undefined || dataConfig.transactions.indexOf('transactions_per_day') === -1) { + this.numTransactions.hidden = true + axes.numTransactions.display = false + } else if (!priceActivated && !marketCapActivated) { + axes.numTransactions.position = 'left' + } + + this.availableSupply = availableSupply + + const txChartTitle = 'Daily transactions' + const marketChartTitle = 'Daily price and market cap' + let chartTitle = '' + if (Object.keys(dataConfig).join() === 'transactions') { + chartTitle = txChartTitle + } else if (Object.keys(dataConfig).join() === 'market') { + chartTitle = marketChartTitle + } + config.options.plugins.title.text = chartTitle + + config.data.datasets = [this.price, this.marketCap, this.numTransactions] + + const isChartLoadedKey = 'isChartLoaded' + const isChartLoaded = window.sessionStorage.getItem(isChartLoadedKey) === 'true' + if (isChartLoaded) { + config.options.animation = false + } else { + window.sessionStorage.setItem(isChartLoadedKey, true) + } + + this.chart = new Chart(el, config) + } + + updateMarketHistory (availableSupply, marketHistoryData) { + this.price.data = getPriceData(marketHistoryData) + if (this.availableSupply !== null && typeof this.availableSupply === 'object') { + const today = new Date().toJSON().slice(0, 10) + this.availableSupply[today] = availableSupply + this.marketCap.data = getMarketCapData(marketHistoryData, this.availableSupply) + } else { + this.marketCap.data = getMarketCapData(marketHistoryData, availableSupply) + } + this.chart.update() + } + + updateTransactionHistory (transactionHistory) { + this.numTransactions.data = getTxHistoryData(transactionHistory) + this.chart.update() + } +} + +export function createMarketHistoryChart (el) { + const dataPaths = $(el).data('history_chart_paths') + const dataConfig = $(el).data('history_chart_config') + + const $chartError = $('[data-chart-error-message]') + const chart = new MarketHistoryChart(el, 0, [], dataConfig) + Object.keys(dataPaths).forEach(function (historySource) { + $.getJSON(dataPaths[historySource], { type: 'JSON' }) + .done(data => { + switch (historySource) { + case 'market': { + const availableSupply = JSON.parse(data.supply_data) + const marketHistoryData = humps.camelizeKeys(JSON.parse(data.history_data)) + + $(el).show() + chart.updateMarketHistory(availableSupply, marketHistoryData) + break + } + case 'transaction': { + const txsHistoryData = JSON.parse(data.history_data) + + $(el).show() + chart.updateTransactionHistory(txsHistoryData) + break + } + } + }) + .fail(() => { + $chartError.show() + }) + }) + return chart +} + +$('[data-chart-error-message]').on('click', _event => { + $('[data-chart-error-message]').hide() + createMarketHistoryChart($('[data-chart="historyChart"]')[0]) +}) diff --git a/apps/block_scout_web/assets/js/lib/indexing.js b/apps/block_scout_web/assets/js/lib/indexing.js new file mode 100644 index 0000000..e406d35 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/indexing.js @@ -0,0 +1,50 @@ +import $ from 'jquery' +import humps from 'humps' +import numeral from 'numeral' +import socket from '../socket' + +function tryUpdateIndexedStatus (el, indexedRatioBlocks = el.dataset.indexedRatioBlocks, indexedRatio = el.dataset.indexedRatio, indexingFinished = false) { + if (indexingFinished) return $("[data-selector='indexed-status']").remove() + 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%') { + 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 + } +} + +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 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/infinite_scroll_helpers.js b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js new file mode 100644 index 0000000..d43b1d4 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js @@ -0,0 +1,107 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import { connectElements } from './redux_helpers.js' + +const initialState = { + loadingNextPage: false, + pagingError: false, + nextPageUrl: null +} + +function infiniteScrollReducer (state = initialState, action) { + switch (action.type) { + case 'INFINITE_SCROLL_ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'LOADING_NEXT_PAGE': { + return Object.assign({}, state, { + loadingNextPage: true + }) + } + case 'PAGING_ERROR': { + return Object.assign({}, state, { + loadingNextPage: false, + pagingError: true + }) + } + case 'RECEIVED_NEXT_PAGE': { + return Object.assign({}, state, { + loadingNextPage: false, + nextPageUrl: action.msg.nextPageUrl + }) + } + default: + return state + } +} + +const elements = { + '[data-selector="next-page-button"]': { + load ($el) { + return { + nextPageUrl: `${$el.hide().attr('href')}&type=JSON` + } + } + }, + '[data-selector="loading-next-page"]': { + render ($el, state) { + if (state.loadingNextPage) { + $el.show() + } else { + $el.hide() + } + } + }, + '[data-selector="paging-error-message"]': { + render ($el, state) { + if (state.pagingError) { + $el.show() + } + } + } +} + +export function withInfiniteScroll (reducer) { + return (state, action) => { + return infiniteScrollReducer(reducer(state, action), action) + } +} + +export function connectInfiniteScroll (store) { + connectElements({ store, elements, action: 'INFINITE_SCROLL_ELEMENTS_LOAD' }) + + onScrollBottom(() => { + const { loadingNextPage, nextPageUrl, pagingError } = store.getState() + if (!loadingNextPage && nextPageUrl && !pagingError) { + store.dispatch({ + type: 'LOADING_NEXT_PAGE' + }) + $.get(nextPageUrl) + .done(msg => { + store.dispatch({ + type: 'RECEIVED_NEXT_PAGE', + msg: humps.camelizeKeys(msg) + }) + }) + .fail(() => { + store.dispatch({ + type: 'PAGING_ERROR' + }) + }) + } + }) +} + +function onScrollBottom (callback) { + const $window = $(window) + function infiniteScrollChecker () { + const scrollHeight = $(document).height() + const scrollPosition = $window.height() + $window.scrollTop() + if ((scrollHeight - scrollPosition) / scrollHeight === 0) { + callback() + } + } + infiniteScrollChecker() + $window.on('scroll', infiniteScrollChecker) +} diff --git a/apps/block_scout_web/assets/js/lib/list_morph.js b/apps/block_scout_web/assets/js/lib/list_morph.js new file mode 100644 index 0000000..08dba71 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/list_morph.js @@ -0,0 +1,130 @@ +import $ from 'jquery' +import map from 'lodash.map' +import get from 'lodash.get' +import noop from 'lodash.noop' +import find from 'lodash.find' +import intersectionBy from 'lodash.intersectionby' +import differenceBy from 'lodash.differenceby' +import morph from 'nanomorph' +import { updateAllAges } from './from_now' + +// The goal of this function is to DOM diff lists, so upon completion `container.innerHTML` should be +// equivalent to `newElements.join('')`. +// +// We could simply do `container.innerHTML = newElements.join('')` but that would not be efficient and +// it not animate appropriately. We could also simply use `morph` (or a similar library) on the entire +// list, however that doesn't give us the proper amount of control for animations. +// +// This function will walk though, remove items currently in `container` which are not in the new list. +// Then it will swap the contents of the items that are in both lists in case the items were updated or +// the order changed. Finally, it will add elements to `container` which are in the new list and didn't +// already exist in the DOM. +// +// Params: +// container: the DOM element which contents need replaced +// newElements: a list of elements that need to be put into the container +// options: +// key: the path to the unique identifier of each element +// horizontal: our horizontal animations are handled in CSS, so passing in `true` will not play JS +// animations +export default function (container, newElements, { key, horizontal } = {}) { + if (!container) return + const oldElements = $(container).children().not('.shrink-out').get() + let currentList = map(oldElements, (el) => ({ id: get(el, key), el })) + const newList = map(newElements, (el) => ({ id: get(el, key), el })) + const overlap = intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] })) + // remove old items + const removals = differenceBy(currentList, newList, 'id') + let canAnimate = false && !horizontal && newList.length > 0 // disabled animation in order to speed up UI + removals.forEach(({ el }) => { + if (!canAnimate) return el.remove() + const $el = $(el) + $el.addClass('shrink-out') + setTimeout(() => { slideUpRemove($el) }, 400) + }) + currentList = differenceBy(currentList, removals, 'id') + + // update kept items + currentList = currentList.map(({ el }, i) => { + if (overlap[i]) { + return ({ + id: overlap[i].id, + el: el.outerHTML === overlap[i].el && overlap[i].el.outerHTML ? el : morph(el, overlap[i].el) + }) + } else { + return null + } + }) + .filter(el => el !== null) + + // add new items + const finalList = newList.map(({ id, el }) => get(find(currentList, { id }), 'el', el)).reverse() + canAnimate = false && !horizontal // disabled animation in order to speed up UI + finalList.forEach((el, i) => { + if (el.parentElement) return + if (!canAnimate) return container.insertBefore(el, get(finalList, `[${i - 1}]`)) + if (!get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el) + slideDownBefore($(get(finalList, `[${i - 1}]`)), el) + }) +} + +function slideDownAppend ($container, content) { + smarterSlideDown($(content), { + insert ($el) { + $container.append($el) + } + }) +} +function slideDownBefore ($container, content) { + smarterSlideDown($(content), { + insert ($el) { + $container.before($el) + } + }) +} +function slideUpRemove ($el) { + smarterSlideUp($el, { + complete () { + $el.remove() + } + }) +} + +function smarterSlideDown ($el, { insert = noop } = {}) { + if (!$el.length) return + const originalScrollHeight = document.body.scrollHeight + const scrollPosition = window.scrollY + + insert($el) + + const isAboveViewport = $el.offset().top < scrollPosition + + if (isAboveViewport) { + const heightDiffAfterInsert = document.body.scrollHeight - originalScrollHeight + const scrollPositionToMaintainViewport = scrollPosition + heightDiffAfterInsert + + $(window).scrollTop(scrollPositionToMaintainViewport) + } else { + $el.hide() + $el.slideDown({ easing: 'linear' }) + } +} + +function smarterSlideUp ($el, { complete = noop } = {}) { + if (!$el.length) return + const originalScrollHeight = document.body.scrollHeight + const scrollPosition = window.scrollY + const isAboveViewport = $el.offset().top + $el.outerHeight(true) < scrollPosition + + if (isAboveViewport) { + $el.hide() + + const heightDiffAfterHide = document.body.scrollHeight - originalScrollHeight + const scrollPositionToMaintainViewport = scrollPosition + heightDiffAfterHide + + $(window).scrollTop(scrollPositionToMaintainViewport) + complete() + } else { + $el.slideUp({ complete, easing: 'linear' }) + } +} diff --git a/apps/block_scout_web/assets/js/lib/loading_element.js b/apps/block_scout_web/assets/js/lib/loading_element.js new file mode 100644 index 0000000..532eb1c --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/loading_element.js @@ -0,0 +1,5 @@ +import $ from 'jquery' + +$('button[data-loading="animation"]').click(_event => { + $('#loading').removeClass('d-none') +}) diff --git a/apps/block_scout_web/assets/js/lib/modals.js b/apps/block_scout_web/assets/js/lib/modals.js new file mode 100644 index 0000000..97f3c20 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/modals.js @@ -0,0 +1,196 @@ +import $ from 'jquery' + +let $currentModal = null +let modalLocked = false + +const spinner = + ` + + + + + ` + +$(document.body).on('hide.bs.modal', e => { + if (modalLocked) { + e.preventDefault() + e.stopPropagation() + return false + } + + $currentModal = null +}) + +export function currentModal () { + return $currentModal +} + +export function openModal ($modal, unclosable) { + // Hide all tooltips before showing a modal, + // since they are sticking on top of modal + $('.tooltip').tooltip('hide') + + if (unclosable) { + $('.close-modal, .modal-status-button-wrapper', $modal).addClass('hidden') + $('.modal-status-text', $modal).addClass('m-b-0') + } + + if ($currentModal) { + modalLocked = false + + $currentModal + .one('hidden.bs.modal', () => { + $modal.modal('show') + $currentModal = $modal + if (unclosable) { + modalLocked = true + } + }) + .modal('hide') + } else { + $modal.modal('show') + $currentModal = $modal + if (unclosable) { + modalLocked = true + } + } +} + +export function openModalWithMessage ($modal, unclosable, message) { + $modal.find('.modal-message').text(message) + openModal($modal, unclosable) +} + +export function lockModal ($modal, $submitButton = null, spinnerText = '') { + $modal.find('.close-modal').attr('disabled', true) + + const $button = $submitButton || $modal.find('.btn-add-full') + + $button + .attr('data-text', $button.text()) + .attr('disabled', true) + + const $span = $('span', $button) + const waitHtml = spinner + (spinnerText ? ` ${spinnerText}` : '') + + if ($span.length) { + $('svg', $button).hide() + $span.html(waitHtml) + } else { + $button.html(waitHtml) + } + + modalLocked = true +} + +export function unlockModal ($modal, $submitButton = null) { + $modal.find('.close-modal').attr('disabled', false) + + const $button = $submitButton || $modal.find('.btn-add-full') + const buttonText = $button.attr('data-text') + + $button.attr('disabled', false) + + const $span = $('span', $button) + if ($span.length) { + $('svg', $button).show() + $span.text(buttonText) + } else { + $button.text(buttonText) + } + + modalLocked = false +} + +export function openErrorModal (title, text, unclosable) { + const $modal = $('#errorStatusModal') + $modal.find('.modal-status-title').text(title) + $modal.find('.modal-status-text').html(text) + openModal($modal, unclosable) +} + +export function openWarningModal (title, text) { + const $modal = $('#warningStatusModal') + $modal.find('.modal-status-title').text(title) + $modal.find('.modal-status-text').html(text) + openModal($modal) +} + +export function openSuccessModal (title, text) { + const $modal = $('#successStatusModal') + $modal.find('.modal-status-title').text(title) + $modal.find('.modal-status-text').html(text) + openModal($modal) +} + +export function openQuestionModal (title, text, acceptCallback = null, exceptCallback = null, acceptText = 'Yes', exceptText = 'No') { + const $modal = $('#questionStatusModal') + const $closeButton = $modal.find('.close-modal') + + $closeButton.attr('disabled', false) + + $modal.find('.modal-status-title').text(title) + $modal.find('.modal-status-text').text(text) + + const $accept = $modal.find('.btn-line.accept') + const $except = $modal.find('.btn-line.except') + + $accept + .removeAttr('data-dismiss') + .removeAttr('disabled') + .unbind('click') + .find('.btn-line-text').text(acceptText) + + $except + .removeAttr('data-dismiss') + .removeAttr('disabled') + .unbind('click') + .find('.btn-line-text').text(exceptText) + + if (acceptCallback) { + $accept.on('click', event => { + $closeButton.attr('disabled', true) + + $accept + .unbind('click') + .attr('disabled', true) + .find('.btn-line-text').html(spinner) + $except + .unbind('click') + .removeAttr('data-dismiss') + .attr('disabled', true) + + modalLocked = true + acceptCallback($modal, event) + }) + } else { + $accept.attr('data-dismiss', 'modal') + } + + if (exceptCallback) { + $except.on('click', event => { + $closeButton.attr('disabled', true) + + $except + .unbind('click') + .attr('disabled', true) + .find('.btn-line-text').html(spinner) + $accept + .unbind('click') + .attr('disabled', true) + .removeAttr('data-dismiss') + + modalLocked = true + exceptCallback($modal, event) + }) + } else { + $except.attr('data-dismiss', 'modal') + } + + openModal($modal) +} + +export function openQrModal () { + const $modal = $('#qrModal') + openModal($modal) +} diff --git a/apps/block_scout_web/assets/js/lib/pending_transactions_toggle.js b/apps/block_scout_web/assets/js/lib/pending_transactions_toggle.js new file mode 100644 index 0000000..26c2846 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/pending_transactions_toggle.js @@ -0,0 +1,20 @@ +import $ from 'jquery' + +const pendingTransactionToggle = (element) => { + const $element = $(element) + const $pendingTransactionsClose = $element.find("[data-selector='pending-transactions-close']") + const $pendingTransactionsOpen = $element.find("[data-selector='pending-transactions-open']") + + $element.on('show.bs.collapse', () => { + $pendingTransactionsOpen.addClass('d-none') + $pendingTransactionsClose.removeClass('d-none') + }) + + $element.on('hide.bs.collapse', () => { + $pendingTransactionsClose.addClass('d-none') + $pendingTransactionsOpen.removeClass('d-none') + }) +} + +// Initialize the script scoped for each instance. +$("[data-selector='pending-transactions-toggle']").each((_index, element) => pendingTransactionToggle(element)) diff --git a/apps/block_scout_web/assets/js/lib/pretty_json.js b/apps/block_scout_web/assets/js/lib/pretty_json.js new file mode 100644 index 0000000..33a921d --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/pretty_json.js @@ -0,0 +1,9 @@ +import $ from 'jquery' + +function prettyPrint (element) { + const jsonString = element.dataset.json + const pretty = JSON.stringify(JSON.parse(jsonString), undefined, 2) + element.innerHTML = pretty +} + +$('[data-json]').each((_index, element) => prettyPrint(element)) 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 0000000..a1f88a3 --- /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/queue.js b/apps/block_scout_web/assets/js/lib/queue.js new file mode 100644 index 0000000..beb5a61 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/queue.js @@ -0,0 +1,72 @@ +/* + +Queue.js + +A function to represent a queue + +Created by Kate Morley - http://code.iamkate.com/ - and released under the terms +of the CC0 1.0 Universal legal code: + +http://creativecommons.org/publicdomain/zero/1.0/legalcode + +*/ + +/* eslint-disable */ + +/* Creates a new queue. A queue is a first-in-first-out (FIFO) data structure - + * items are added to the end of the queue and removed from the front. + */ +export default function Queue(){ + + // initialise the queue and offset + var queue = []; + var offset = 0; + + // Returns the length of the queue. + this.getLength = function(){ + return (queue.length - offset); + } + + // Returns true if the queue is empty, and false otherwise. + this.isEmpty = function(){ + return (queue.length == 0); + } + + /* Enqueues the specified item. The parameter is: + * + * item - the item to enqueue + */ + this.enqueue = function(item){ + queue.push(item); + } + + /* Dequeues an item and returns it. If the queue is empty, the value + * 'undefined' is returned. + */ + this.dequeue = function(){ + + // if the queue is empty, return immediately + if (queue.length == 0) return undefined; + + // store the item at the front of the queue + var item = queue[offset]; + + // increment the offset and remove the free space if necessary + if (++ offset * 2 >= queue.length){ + queue = queue.slice(offset); + offset = 0; + } + + // return the dequeued item + return item; + + } + + /* Returns the item at the front of the queue (without dequeuing it). If the + * queue is empty then undefined is returned. + */ + this.peek = function(){ + return (queue.length > 0 ? queue[offset] : undefined); + } + +} 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 new file mode 100644 index 0000000..bc36d26 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/random_access_pagination.js @@ -0,0 +1,369 @@ +import $ from 'jquery' +import map from 'lodash.map' +import merge from 'lodash.merge' +import humps from 'humps' +import URI from 'urijs' +import listMorph from '../lib/list_morph' +import reduceReducers from 'reduce-reducers' +import { createStore, connectElements } from '../lib/redux_helpers.js' +import '../app' + +const maxPageNumberInOneLine = 7 +const groupedPagesNumber = 3 + +/** + * + * This module is a clone of async_listing_load.js adapted for pagination with random access + * + */ + +let enableFirstLoading = true + +export const asyncInitialState = { + /* it will consider any query param in the current URI as paging */ + beyondPageOne: false, + /* will be sent along with { type: 'JSON' } to controller, useful for dynamically changing parameters */ + additionalParams: {}, + /* an array with every html element of the list being shown */ + items: [], + /* the key for diffing the elements in the items array */ + itemKey: null, + /* represents whether a request is happening or not */ + loading: false, + /* if there was an error fetching items */ + requestError: false, + /* if response has no items */ + emptyResponse: false, + /* current's page number */ + currentPageNumber: 0 +} + +export function asyncReducer (state = asyncInitialState, action) { + switch (action.type) { + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, { + nextPagePath: action.nextPagePath, + currentPagePath: action.nextPagePath + }) + } + case 'ADD_ITEM_KEY': { + return Object.assign({}, state, { itemKey: action.itemKey }) + } + case 'START_REQUEST': { + let pageNumber = state.currentPageNumber + if (action.pageNumber) { pageNumber = parseInt(action.pageNumber) } + + return Object.assign({}, state, { + loading: true, + requestError: false, + currentPagePath: action.path, + currentPageNumber: pageNumber, + items: generateStub(state.items.length) + }) + } + case 'REQUEST_ERROR': { + return Object.assign({}, state, { requestError: true }) + } + case 'FINISH_REQUEST': { + return Object.assign({}, state, { + loading: false + }) + } + case 'ITEMS_FETCHED': { + if (action.nextPageParams !== null) { + const pageNumber = parseInt(action.nextPageParams.pageNumber) + if (typeof action.path !== 'undefined') { + history.replaceState({}, null, URI(action.path).query(humps.decamelizeKeys(action.nextPageParams))) + } + delete action.nextPageParams.pageNumber + + return Object.assign({}, state, { + requestError: false, + emptyResponse: action.items.length === 0, + items: action.items, + nextPageParams: humps.decamelizeKeys(action.nextPageParams), + pagesLimit: parseInt(action.nextPageParams.pagesLimit), + currentPageNumber: pageNumber, + beyondPageOne: pageNumber !== 1 + }) + } + return Object.assign({}, state, { + requestError: false, + emptyResponse: action.items.length === 0, + items: action.items, + nextPageParams: humps.decamelizeKeys(action.nextPageParams), + pagesLimit: 1, + currentPageNumber: 1, + beyondPageOne: false + }) + } + default: + return state + } +} + +export const elements = { + '[data-async-listing]': { + load ($el) { + const nextPagePath = $el.data('async-listing') + + return { nextPagePath } + } + }, + '[data-async-listing] [data-loading-message]': { + render ($el, state) { + if (state.loading) return $el.show() + + $el.hide() + } + }, + '[data-async-listing] [data-empty-response-message]': { + render ($el, state) { + if ( + !state.requestError && + (!state.loading) && + state.items.length === 0 + ) { + return $el.show() + } + + $el.hide() + } + }, + '[data-async-listing] [data-error-message]': { + render ($el, state) { + if (state.requestError) return $el.show() + + $el.hide() + } + }, + '[data-async-listing] [data-items]': { + render ($el, state, oldState) { + if (state.items === oldState.items) return + + if (state.itemKey) { + const container = $el[0] + const newElements = map(state.items, (item) => $(item)[0]) + listMorph(container, newElements, { key: state.itemKey }) + return + } + + $el.html(state.items) + } + }, + '[data-async-listing] [data-next-page-button]': { + render ($el, state) { + if (state.emptyResponse) { + return $el.hide() + } + + $el.show() + if (state.requestError || state.currentPageNumber >= state.pagesLimit || state.loading) { + return $el.attr('disabled', 'disabled') + } + + $el.attr('disabled', false) + $el.attr('href', state.nextPagePath) + } + }, + '[data-async-listing] [data-prev-page-button]': { + render ($el, state) { + if (state.emptyResponse) { + return $el.hide() + } + + $el.show() + if (state.requestError || state.currentPageNumber <= 1 || state.loading) { + return $el.attr('disabled', 'disabled') + } + + $el.attr('disabled', false) + $el.attr('href', state.prevPagePath) + } + }, + '[data-async-listing] [pages-numbers-container]': { + render ($el, state) { + if (typeof state.pagesLimit !== 'undefined') { pagesNumbersGenerate(state.pagesLimit, $el, state.currentPageNumber, state.loading) } + } + }, + '[data-async-listing] [data-loading-button]': { + render ($el, state) { + if (state.loading) return $el.show() + + $el.hide() + } + }, + '[data-async-listing] [data-pagination-container]': { + render ($el, state) { + if (state.emptyResponse) { + return $el.hide() + } + + $el.show() + } + }, + '[csv-download]': { + render ($el, state) { + if (state.emptyResponse) { + return $el.hide() + } + return $el.show() + } + } +} + +/** + * Create a store combining the given reducer and initial state with the async reducer. + * + * reducer: The reducer that will be merged with the asyncReducer to add async + * loading capabilities to a page. Any state changes in the reducer passed will be + * applied AFTER the asyncReducer. + * + * initialState: The initial state to be merged with the async state. Any state + * values passed here will overwrite the values on asyncInitialState. + * + * itemKey: it will be added to the state as the key for diffing the elements and + * adding or removing with the correct animation. Check list_morph.js for more informantion. + */ +export function createAsyncLoadStore (reducer, initialState, itemKey) { + const state = merge(asyncInitialState, initialState) + const store = createStore(reduceReducers(state, asyncReducer, reducer)) + + if (typeof itemKey !== 'undefined') { + store.dispatch({ + type: 'ADD_ITEM_KEY', + itemKey + }) + } + + connectElements({ store, elements }) + firstPageLoad(store) + return store +} + +export function refreshPage (store) { + loadPageByNumber(store, store.getState().currentPageNumber) +} + +export function loadPageByNumber (store, pageNumber) { + const path = $('[data-async-listing]').data('async-listing') + store.dispatch({ type: 'START_REQUEST', path, pageNumber }) + if (URI(path).query() !== '' && typeof store.getState().nextPageParams === 'undefined') { + $.getJSON(path, { type: 'JSON' }) + .done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED', path }, humps.camelizeKeys(response)))) + .fail(() => store.dispatch({ type: 'REQUEST_ERROR' })) + .always(() => store.dispatch({ type: 'FINISH_REQUEST' })) + } else { + $.getJSON(URI(path).path(), merge({ type: 'JSON', page_number: pageNumber }, store.getState().nextPageParams)) + .done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED', path }, humps.camelizeKeys(response)))) + .fail(() => store.dispatch({ type: 'REQUEST_ERROR' })) + .always(() => store.dispatch({ type: 'FINISH_REQUEST' })) + } +} + +function firstPageLoad (store) { + const $element = $('[data-async-listing]') + function loadItemsNext () { + loadPageByNumber(store, store.getState().currentPageNumber + 1) + } + + function loadItemsPrev () { + loadPageByNumber(store, store.getState().currentPageNumber - 1) + } + + if (enableFirstLoading) { + loadItemsNext() + } + + $element.on('click', '[data-error-message]', (event) => { + event.preventDefault() + loadItemsNext() + }) + + $element.on('click', '[data-next-page-button]', (event) => { + event.preventDefault() + loadItemsNext() + }) + + $element.on('click', '[data-prev-page-button]', (event) => { + event.preventDefault() + loadItemsPrev() + }) + + $element.on('click', '[data-page-number]', (event) => { + event.preventDefault() + loadPageByNumber(store, event.target.dataset.pageNumber) + }) + + $element.on('submit', '[input-page-number-form]', (event) => { + event.preventDefault() + const $input = event.target.querySelector('#page-number') + const input = parseInt($input.value) + const loading = store.getState().loading + const pagesLimit = store.getState().pagesLimit + if (!isNaN(input) && input <= pagesLimit && !loading) { loadPageByNumber(store, input) } + if (!loading || isNaN(input) || input > pagesLimit) { $input.value = '' } + return false + }) +} + +const $element = $('[data-async-load]') +if ($element.length) { + if (Object.prototype.hasOwnProperty.call($element.data(), 'noFirstLoading')) { + enableFirstLoading = false + } + if (enableFirstLoading) { + const store = createStore(asyncReducer) + connectElements({ store, elements }) + firstPageLoad(store) + } +} + +function pagesNumbersGenerate (pagesLimit, $container, currentPageNumber, loading) { + let resultHTML = '' + if (pagesLimit < 1) { return } + if (pagesLimit <= maxPageNumberInOneLine) { + resultHTML = renderPaginationElements(1, pagesLimit, currentPageNumber, loading) + } else if (currentPageNumber < groupedPagesNumber) { + resultHTML += renderPaginationElements(1, groupedPagesNumber, currentPageNumber, loading) + resultHTML += renderPaginationElement('...', false, loading) + resultHTML += renderPaginationElement(pagesLimit, currentPageNumber === pagesLimit, loading) + } else if (currentPageNumber > pagesLimit - groupedPagesNumber) { + resultHTML += renderPaginationElement(1, currentPageNumber === 1, loading) + resultHTML += renderPaginationElement('...', false, loading) + resultHTML += renderPaginationElements(pagesLimit - groupedPagesNumber, pagesLimit, currentPageNumber, loading) + } else { + resultHTML += renderPaginationElement(1, currentPageNumber === 1, loading) + const step = parseInt(groupedPagesNumber / 2) + if (currentPageNumber - step - 1 === 2) { + resultHTML += renderPaginationElement(2, currentPageNumber === 2, loading) + } else if (currentPageNumber - step > 2) { + resultHTML += renderPaginationElement('...', false, loading) + } + resultHTML += renderPaginationElements(currentPageNumber - step, currentPageNumber + step, currentPageNumber, loading) + if (currentPageNumber + step + 1 === pagesLimit - 1) { + resultHTML += renderPaginationElement(pagesLimit - 1, pagesLimit - 1 === currentPageNumber, loading) + } else if (currentPageNumber + step < pagesLimit - 1) { + resultHTML += renderPaginationElement('...', false, loading) + } + resultHTML += renderPaginationElement(pagesLimit, currentPageNumber === pagesLimit, loading) + } + $container.html(resultHTML) +} + +function renderPaginationElements (start, end, currentPageNumber, loading) { + let resultHTML = '' + for (let i = start; i <= end; i++) { + resultHTML += renderPaginationElement(i, i === currentPageNumber, loading) + } + return resultHTML +} + +function renderPaginationElement (text, active, loading) { + return '
  • ' + text + '
  • ' +} + +function generateStub (size) { + const stub = '
    ' + return Array.from(Array(size > 10 ? 10 : 10), () => stub) // I decided to always put 10 lines in order to make page lighter +} diff --git a/apps/block_scout_web/assets/js/lib/redux_helpers.js b/apps/block_scout_web/assets/js/lib/redux_helpers.js new file mode 100644 index 0000000..93af7f8 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/redux_helpers.js @@ -0,0 +1,128 @@ +import $ from 'jquery' +import reduce from 'lodash.reduce' +import isObject from 'lodash.isobject' +import forIn from 'lodash.forin' +import { createStore as reduxCreateStore } from 'redux' + +/** + * Create a redux store given the reducer. It also enables the Redux dev tools. + */ +export function createStore (reducer) { + return reduxCreateStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) +} + +/** + * Connect elements with the redux store. It must receive an object with the following attributes: + * + * elements: It is an object with elements that are going to react to the redux state or add something + * to the initial state. + * + * ```javascript + * const elements = { + * // The JQuery selector for finding elements in the page. + * '[data-counter]': { + * // Useful to put things from the page to the redux state. + * load ($element) {...}, + * // Check for state changes and manipulates the DOM accordingly. + * render ($el, state, oldState) {...} + * } + * } + * ``` + * + * The load and render functions are optional, you can have both or just one of them. It depends + * on if you want to load something to the state in the first render and/or that the element should + * react to the redux state. Notice that you can include more elements if you want to since elements + * also is an object. + * + * store: It is the redux store that the elements should be connected with. + * ```javascript + * const store = createStore(reducer) + * ``` + * + * action: The first action that the store is going to dispatch. Optional, by default 'ELEMENTS_LOAD' + * is going to be dispatched. + * + * ### Examples + * + * Given the markup: + * ```HTML + *
    + * 1 + *
    + * ``` + * + * The reducer: + * ```javascript + * function reducer (state = { number: null }, action) { + * switch (action.type) { + * case 'ELEMENTS_LOAD': { + * return Object.assign({}, state, { number: action.number }) + * } + * case 'INCREMENT': { + * return Object.assign({}, state, { number: state.number + 1 }) + * } + * default: + * return state + * } + * } + * ``` + * + * The elements: + * ```javascript + * const elements = { + * // '[data-counter]' is the element that will be connected to the redux store. + * '[data-counter]': { + * // Find the number within data-counter and add to the state. + * load ($el) { + * return { number: $el.find('.number').val() } + * }, + * // React to redux state. Case the number in the state changes, it is going to render the + * // new number. + * render ($el, state, oldState) { + * if (state.number === oldState.number) return + * + * $el.html(state.number) + * } + * } + * } + * + * All we need to do is connecting the store and the elements using this function. + * ```javascript + * connectElements({store, elements}) + * ``` + * + * Now, if we dispatch the `INCREMENT` action, the state is going to change and the [data-counter] + * element is going to re-render since they are connected. + * ```javascript + * store.dispatch({type: 'INCREMENT'}) + * ``` + */ +export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) { + function loadElements () { + return reduce(elements, (pageLoadParams, { load }, selector) => { + if (!load) return pageLoadParams + const $el = $(selector) + if (!$el.length) return pageLoadParams + const morePageLoadParams = load($el, store) + return isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams + }, {}) + } + + function renderElements (state, oldState) { + forIn(elements, ({ render }, selector) => { + if (!render) return + const $el = $(selector) + if (!$el.length) return + render($el, state, oldState) + }) + } + + let oldState = store.getState() + store.subscribe(() => { + const state = store.getState() + renderElements(state, oldState) + oldState = state + }) + + store.dispatch(Object.assign(loadElements(), { type: action })) +} diff --git a/apps/block_scout_web/assets/js/lib/reload_button.js b/apps/block_scout_web/assets/js/lib/reload_button.js new file mode 100644 index 0000000..1cb71be --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/reload_button.js @@ -0,0 +1,3 @@ +import $ from 'jquery' + +$('[data-selector="reload-button"]').click(() => window.location.reload()) diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js new file mode 100644 index 0000000..9b191e1 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js @@ -0,0 +1,312 @@ +import Web3 from 'web3' +import $ from 'jquery' +import { props } from 'eth-net-props' + +export const connectSelector = '[connect-wallet]' +export const disconnectSelector = '[disconnect-wallet]' +const connectToSelector = '[connect-to]' +const connectedToSelector = '[connected-to]' + +export function getContractABI ($form) { + const implementationAbi = $form.data('implementation-abi') + const parentAbi = $form.data('contract-abi') + const $parent = $('[data-smart-contract-functions]') + const contractType = $parent.data('type') + const contractAbi = contractType === 'proxy' ? implementationAbi : parentAbi + return contractAbi +} + +export function getMethodInputs (contractAbi, functionName) { + const functionAbi = contractAbi.find(abi => + abi.name === functionName + ) + return functionAbi && functionAbi.inputs +} + +export function prepareMethodArgs ($functionInputs, inputs) { + return $.map($functionInputs, (element, ind) => { + const inputValue = $(element).val() + const inputType = inputs[ind] && inputs[ind].type + const inputComponents = inputs[ind] && inputs[ind].components + let sanitizedInputValue + sanitizedInputValue = replaceDoubleQuotes(inputValue, inputType, inputComponents) + sanitizedInputValue = replaceSpaces(sanitizedInputValue, inputType, inputComponents) + + if (isArrayInputType(inputType) || isTupleInputType(inputType)) { + if (sanitizedInputValue === '' || sanitizedInputValue === '[]') { + return [[]] + } else { + if (isArrayOfTuple(inputType)) { + const sanitizedInputValueElements = JSON.parse(sanitizedInputValue).map((elementValue, index) => { + return sanitizeMutipleInputValues(elementValue, inputType, inputComponents) + }) + return [sanitizedInputValueElements] + } else { + if (sanitizedInputValue.startsWith('[') && sanitizedInputValue.endsWith(']')) { + sanitizedInputValue = sanitizedInputValue.substring(1, sanitizedInputValue.length - 1) + } + const inputValueElements = sanitizedInputValue.split(',') + const sanitizedInputValueElements = sanitizeMutipleInputValues(inputValueElements, inputType, inputComponents) + return [sanitizedInputValueElements] + } + } + } else { return convertToBool(sanitizedInputValue, inputType) } + }) +} + +function sanitizeMutipleInputValues (inputValueElements, inputType, inputComponents) { + return inputValueElements.map((elementValue, index) => { + let elementInputType + if (inputType.includes('tuple')) { + elementInputType = inputComponents[index].type + } else { + elementInputType = inputType.split('[')[0] + } + + let sanitizedElementValue = replaceDoubleQuotes(elementValue, elementInputType) + sanitizedElementValue = replaceSpaces(sanitizedElementValue, elementInputType) + sanitizedElementValue = convertToBool(sanitizedElementValue, elementInputType) + + return sanitizedElementValue + }) +} + +export function compareChainIDs (explorerChainId, walletChainIdHex) { + if (explorerChainId !== parseInt(walletChainIdHex)) { + const networkDisplayNameFromWallet = props.getNetworkDisplayName(walletChainIdHex) + const networkDisplayName = props.getNetworkDisplayName(explorerChainId) + const errorMsg = `You connected to ${networkDisplayNameFromWallet} chain in the wallet, but the current instance of Blockscout is for ${networkDisplayName} chain` + return Promise.reject(new Error(errorMsg)) + } else { + return Promise.resolve() + } +} + +export const formatError = (error) => { + let { message } = error + message = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message + return message +} + +export const formatTitleAndError = (error) => { + let { message } = error + let title = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message + title = title && title.split('{').length > 1 ? title.split('{')[0].replace(':', '') : title + let txHash = '' + let errorMap = '' + try { + errorMap = message && message.indexOf('{') >= 0 ? JSON.parse(message && message.slice(message.indexOf('{'))) : '' + message = errorMap.error || '' + txHash = errorMap.transactionHash || '' + } catch (exception) { + message = '' + } + return { title, message, txHash } +} + +export const getCurrentAccountPromise = (provider) => { + return new Promise((resolve, reject) => { + if (provider && provider.wc) { + getCurrentAccountFromWCPromise(provider) + .then(account => resolve(account)) + .catch(err => { + reject(err) + }) + } else { + getCurrentAccountFromMMPromise() + .then(account => resolve(account)) + .catch(err => { + reject(err) + }) + } + }) +} + +export const getCurrentAccountFromWCPromise = (provider) => { + return new Promise((resolve, reject) => { + // Get a Web3 instance for the wallet + const web3 = new Web3(provider) + + // Get list of accounts of the connected wallet + web3.eth.getAccounts() + .then(accounts => { + // MetaMask does not give you all accounts, only the selected account + resolve(accounts[0]) + }) + .catch(err => { + reject(err) + }) + }) +} + +export const getCurrentAccountFromMMPromise = () => { + return new Promise((resolve, reject) => { + window.ethereum.request({ method: 'eth_accounts' }) + .then(accounts => { + const account = accounts[0] ? accounts[0].toLowerCase() : null + resolve(account) + }) + .catch(err => { + reject(err) + }) + }) +} + +function hideConnectedToContainer () { + document.querySelector(connectedToSelector) && document.querySelector(connectedToSelector).classList.add('hidden') +} + +function showConnectedToContainer () { + document.querySelector(connectedToSelector) && document.querySelector(connectedToSelector).classList.remove('hidden') +} + +function hideConnectContainer () { + document.querySelector(connectSelector) && document.querySelector(connectSelector).classList.add('hidden') +} + +function showConnectContainer () { + document.querySelector(connectSelector) && document.querySelector(connectSelector).classList.remove('hidden') +} + +function hideConnectToContainer () { + document.querySelector(connectToSelector) && document.querySelector(connectToSelector).classList.add('hidden') +} + +function showConnectToContainer () { + document.querySelector(connectToSelector) && document.querySelector(connectToSelector).classList.remove('hidden') +} + +export function showHideDisconnectButton () { + // Show disconnect button only in case of Wallet Connect + if (window.web3 && window.web3.currentProvider && window.web3.currentProvider.wc) { + document.querySelector(disconnectSelector) && document.querySelector(disconnectSelector).classList.remove('hidden') + } else { + document.querySelector(disconnectSelector) && document.querySelector(disconnectSelector).classList.add('hidden') + } +} + +export function showConnectedToElements (account) { + hideConnectToContainer() + showConnectContainer() + showConnectedToContainer() + showHideDisconnectButton() + setConnectToAddress(account) +} + +export function showConnectElements () { + showConnectToContainer() + showConnectContainer() + hideConnectedToContainer() +} + +export function hideConnectButton () { + showConnectToContainer() + hideConnectContainer() + hideConnectedToContainer() +} + +function setConnectToAddress (account) { + if (document.querySelector('[connected-to-address]')) { + document.querySelector('[connected-to-address]').innerHTML = `${trimmedAddressHash(account)}` + } +} + +function trimmedAddressHash (account) { + if ($(window).width() < 544) { + return `${account.slice(0, 7)}–${account.slice(-6)}` + } else { + return account + } +} + +function convertToBool (value, type) { + if (isBoolInputType(type)) { + const boolVal = (value === 'true' || value === '1' || value === 1) + + return boolVal + } else { + return value + } +} + +function isArrayInputType (inputType) { + return inputType && inputType.includes('[') && inputType.includes(']') +} + +function isTupleInputType (inputType) { + return inputType && inputType.includes('tuple') && !isArrayInputType(inputType) +} + +function isArrayOfTuple (inputType) { + return inputType && inputType.includes('tuple') && isArrayInputType(inputType) +} + +function isAddressInputType (inputType) { + return inputType && inputType.includes('address') && !isArrayInputType(inputType) +} + +function isUintInputType (inputType) { + return inputType && inputType.includes('int') && !isArrayInputType(inputType) +} + +function isStringInputType (inputType) { + return inputType && inputType.includes('string') && !isArrayInputType(inputType) +} + +function isBytesInputType (inputType) { + return inputType && inputType.includes('bytes') && !isArrayInputType(inputType) +} + +function isBoolInputType (inputType) { + return inputType && inputType.includes('bool') && !isArrayInputType(inputType) +} + +function isNonSpaceInputType (inputType) { + return isAddressInputType(inputType) || isBytesInputType(inputType) || inputType.includes('int') || inputType.includes('bool') +} + +function replaceSpaces (value, type, components) { + if (isNonSpaceInputType(type) && isFunction(value.replace)) { + return value.replace(/\s/g, '') + } else if (isTupleInputType(type) && isFunction(value.split)) { + return value + .split(',') + .map((itemValue, itemIndex) => { + const itemType = components && components[itemIndex] && components[itemIndex].type + + return replaceSpaces(itemValue, itemType) + }) + .join(',') + } else { + if (typeof value.trim === 'function') { + return value.trim() + } + return value + } +} + +function replaceDoubleQuotes (value, type, components) { + if (isAddressInputType(type) || isUintInputType(type) || isStringInputType(type) || isBytesInputType(type)) { + if (isFunction(value.replaceAll)) { + return value.replaceAll('"', '') + } else if (isFunction(value.replace)) { + return value.replace(/"/g, '') + } + return value + } else if (isTupleInputType(type) && isFunction(value.split)) { + return value + .split(',') + .map((itemValue, itemIndex) => { + const itemType = components && components[itemIndex] && components[itemIndex].type + + return replaceDoubleQuotes(itemValue, itemType) + }) + .join(',') + } else { + return value + } +} + +function isFunction (param) { + return typeof param === 'function' +} 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 new file mode 100644 index 0000000..ca896ef --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/connect.js @@ -0,0 +1,179 @@ +import Web3 from 'web3' +import Web3Modal from 'web3modal' +import WalletConnectProvider from '@walletconnect/web3-provider' +import { compareChainIDs, formatError, showConnectElements, showConnectedToElements } from './common_helpers' +import { openWarningModal } from '../modals' + +const instanceChainIdStr = document.getElementById('js-chain-id').value +const instanceChainId = parseInt(instanceChainIdStr, 10) +const walletConnectOptions = { rpc: {}, chainId: instanceChainId } +const jsonRPC = document.getElementById('js-json-rpc').value +walletConnectOptions.rpc[instanceChainId] = jsonRPC + +// Chosen wallet provider given by the dialog window +let provider + +// Web3modal instance +let web3Modal + +/** + * Setup the orchestra + */ +export async function web3ModalInit (connectToWallet, ...args) { + return new Promise((resolve) => { + // Tell Web3modal what providers we have available. + // Built-in web browser provider (only one can exist as a time) + // like MetaMask, Brave or Opera is added automatically by Web3modal + const providerOptions = { + walletconnect: { + package: WalletConnectProvider, + options: walletConnectOptions + } + } + + web3Modal = new Web3Modal({ + cacheProvider: true, + providerOptions, + disableInjectedProvider: false + }) + + if (web3Modal.cachedProvider) { + connectToWallet(...args) + } + + resolve(web3Modal) + }) +} + +export const walletEnabled = () => { + return new Promise((resolve) => { + if (window.web3 && window.web3.currentProvider && window.web3.currentProvider.wc) { + resolve(true) + } else { + if (window.ethereum) { + window.web3 = new Web3(window.ethereum) + window.ethereum._metamask.isUnlocked() + .then(isUnlocked => { + if (isUnlocked && window.ethereum.isNiftyWallet) { // Nifty Wallet + window.web3 = new Web3(window.web3.currentProvider) + resolve(true) + } else if (isUnlocked === false && window.ethereum.isNiftyWallet) { // Nifty Wallet + window.ethereum.enable() + resolve(false) + } else { + if (window.ethereum.isNiftyWallet) { + window.ethereum.enable() + window.web3 = new Web3(window.web3.currentProvider) + resolve(true) + } else { + return window.ethereum.request({ method: 'eth_requestAccounts' }) + .then((_res) => { + window.web3 = new Web3(window.web3.currentProvider) + resolve(true) + }) + .catch(_error => { + resolve(false) + }) + } + } + }) + .catch(_error => { + resolve(false) + }) + } else if (window.web3) { + window.web3 = new Web3(window.web3.currentProvider) + resolve(true) + } else { + resolve(false) + } + } + }) +} + +export async function disconnect () { + if (provider && provider.close) { + await provider.close() + } + + provider = null + + window.web3 = null + + // If the cached provider is not cleared, + // WalletConnect will default to the existing session + // and does not allow to re-scan the QR code with a new wallet. + // Depending on your use case you may want or want not his behavir. + await web3Modal.clearCachedProvider() +} + +/** + * Disconnect wallet button pressed. + */ +export async function disconnectWallet () { + await disconnect() + + showConnectElements() +} + +export const connectToProvider = () => { + return new Promise((resolve, reject) => { + try { + web3Modal + .connect() + .then((connectedProvider) => { + provider = connectedProvider + window.web3 = new Web3(provider) + resolve(provider) + }) + } catch (e) { + reject(e) + } + }) +} + +export const connectToWallet = async () => { + await connectToProvider() + + // Subscribe to accounts change + provider.on('accountsChanged', async (accs) => { + const newAccount = accs && accs.length > 0 ? accs[0].toLowerCase() : null + + if (!newAccount) { + await disconnectWallet() + } + + fetchAccountData(showConnectedToElements, []) + }) + + // Subscribe to chainId change + provider.on('chainChanged', (chainId) => { + compareChainIDs(instanceChainId, chainId) + .then(() => fetchAccountData(showConnectedToElements, [])) + .catch(error => { + openWarningModal('Unauthorized', formatError(error)) + }) + }) + + provider.on('disconnect', async () => { + await disconnectWallet() + }) + + await fetchAccountData(showConnectedToElements, []) +} + +export async function fetchAccountData (setAccount, args) { + // Get a Web3 instance for the wallet + if (provider) { + window.web3 = new Web3(provider) + } + + // Get list of accounts of the connected wallet + const accounts = window.web3 && await window.web3.eth.getAccounts() + + // MetaMask does not give you all accounts, only the selected account + if (accounts && accounts.length > 0) { + const selectedAccount = accounts[0] + + setAccount(selectedAccount, ...args) + } +} 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 new file mode 100644 index 0000000..6230972 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js @@ -0,0 +1,106 @@ +import $ from 'jquery' +import { connectSelector, disconnectSelector, getContractABI, getMethodInputs, prepareMethodArgs } from './common_helpers' +import { queryMethod, callMethod } from './interact' +import { walletEnabled, connectToWallet, disconnectWallet, web3ModalInit } from './connect.js' +import '../../pages/address' + +const loadFunctions = (element, isCustomABI) => { + const $element = $(element) + const url = $element.data('url') + const hash = $element.data('hash') + const type = $element.data('type') + const action = $element.data('action') + + $.get( + url, + { hash, type, action, is_custom_abi: isCustomABI }, + response => $element.html(response) + ) + .done(function () { + document.querySelector(connectSelector) && document.querySelector(connectSelector).addEventListener('click', connectToWallet) + document.querySelector(disconnectSelector) && document.querySelector(disconnectSelector).addEventListener('click', disconnectWallet) + web3ModalInit(connectToWallet) + + const selector = isCustomABI ? '[data-function-custom]' : '[data-function]' + + $(selector).each((_, element) => { + readWriteFunction(element) + }) + + $('.contract-exponentiation-btn').on('click', (event) => { + const $customPower = $(event.currentTarget).find('[name=custom_power]') + let power + if ($customPower.length > 0) { + power = parseInt($customPower.val(), 10) + } else { + power = parseInt($(event.currentTarget).data('power'), 10) + } + const $input = $(event.currentTarget).parent().parent().parent().find('[name=function_input]') + const currentInputVal = parseInt($input.val(), 10) || 1 + const newInputVal = (currentInputVal * Math.pow(10, power)).toString() + $input.val(newInputVal.toString()) + }) + + $('[name=custom_power]').on('click', (event) => { + $(event.currentTarget).parent().parent().toggleClass('show') + }) + }) + .fail(function (response) { + $element.html(response.statusText) + }) +} + +const readWriteFunction = (element) => { + const $element = $(element) + const $form = $element.find('[data-function-form]') + + const $responseContainer = $element.find('[data-function-response]') + + $form.on('submit', (event) => { + event.preventDefault() + const action = $form.data('action') + const $errorContainer = $form.parent().find('[input-parse-error-container]') + + $errorContainer.hide() + + const $functionInputs = $form.find('input[name=function_input]') + const $functionName = $form.find('input[name=function_name]') + const functionName = $functionName && $functionName.val() + + if (action === 'read') { + const url = $form.data('url') + + const contractAbi = getContractABI($form) + const inputs = getMethodInputs(contractAbi, functionName) + const $methodId = $form.find('input[name=method_id]') + try { + var args = prepareMethodArgs($functionInputs, inputs) + } catch (exception) { + $errorContainer.show() + $errorContainer.text(exception) + 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, isCustomABI)) + } else if (action === 'write') { + const explorerChainId = $form.data('chainId') + walletEnabled() + .then((isWalletEnabled) => callMethod(isWalletEnabled, $functionInputs, explorerChainId, $form, functionName, $element)) + } + }) +} + +const container = $('[data-smart-contract-functions]') + +if (container.length) { + 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/index.js b/apps/block_scout_web/assets/js/lib/smart_contract/index.js new file mode 100644 index 0000000..86458b8 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/index.js @@ -0,0 +1,3 @@ +import './functions' +import './wei_ether_converter' +import '../../app' 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 new file mode 100644 index 0000000..eaacbfe --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/interact.js @@ -0,0 +1,115 @@ +import $ from 'jquery' +import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals' +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, isCustomABI) => { + const data = { + function_name: functionName, + method_id: $methodId.val(), + type, + is_custom_abi: isCustomABI + } + + data.args_count = args.length + let i = args.length + + while (i--) { + data['arg_' + i] = args[i] + } + + if (isWalletEnabled) { + getCurrentAccountPromise(window.web3 && window.web3.currentProvider) + .then((currentAccount) => { + data.from = currentAccount + $.get(url, data, response => $responseContainer.html(response)) + } + ) + } else { + $.get(url, data, response => $responseContainer.html(response)) + } +} + +export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $form, functionName, $element) => { + if (!isWalletEnabled) { + const warningMsg = 'Wallet is not connected.' + return openWarningModal('Unauthorized', warningMsg) + } + const contractAbi = getContractABI($form) + const inputs = getMethodInputs(contractAbi, functionName) + + const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])') + const args = prepareMethodArgs($functionInputsExceptTxValue, inputs) + + const txValue = getTxValue($functionInputs) + const contractAddress = $form.data('contract-address') + + window.web3.eth.getChainId() + .then((walletChainId) => { + compareChainIDs(explorerChainId, walletChainId) + .then(() => getCurrentAccountPromise(window.web3.currentProvider)) + .catch(error => { + openWarningModal('Unauthorized', formatError(error)) + }) + .then((currentAccount) => { + if (functionName) { + const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) + const sendParams = { from: currentAccount, value: txValue || 0 } + const methodToCall = TargetContract.methods[functionName](...args).send(sendParams) + methodToCall + .on('error', function (error) { + const titleAndError = formatTitleAndError(error) + const message = titleAndError.message + (titleAndError.txHash ? `
    More info` : '') + openErrorModal(titleAndError.title.length ? titleAndError.title : `Error in sending transaction for method "${functionName}"`, message, false) + }) + .on('transactionHash', function (txHash) { + onTransactionHash(txHash, functionName) + }) + } else { + const txParams = { + from: currentAccount, + to: contractAddress, + value: txValue || 0 + } + window.ethereum.request({ + method: 'eth_sendTransaction', + params: [txParams] + }) + .then(function (txHash) { + onTransactionHash(txHash, functionName) + }) + .catch(function (error) { + openErrorModal('Error in sending transaction for fallback method', formatError(error), false) + }) + } + }) + .catch(error => { + openWarningModal('Unauthorized', formatError(error)) + }) + }) +} + +function onTransactionHash (txHash, functionName) { + openModalWithMessage($('#pending-contract-write'), true, txHash) + const getTxReceipt = (txHash) => { + window.ethereum.request({ + method: 'eth_getTransactionReceipt', + params: [txHash] + }) + .then(txReceipt => { + if (txReceipt) { + const successMsg = `Successfully sent transaction for method "${functionName}"` + openSuccessModal('Success', successMsg) + clearInterval(txReceiptPollingIntervalId) + } + }) + } + const txReceiptPollingIntervalId = setInterval(() => { getTxReceipt(txHash) }, 5 * 1000) +} + +const ethStrToWeiBn = ethStr => BigNumber(ethStr).multipliedBy(10 ** 18) + +function getTxValue ($functionInputs) { + const txValueEth = $functionInputs.filter('[tx-value]:first')?.val() || '0' + return `0x${ethStrToWeiBn(txValueEth).toString(16)}` +} diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/wei_ether_converter.js b/apps/block_scout_web/assets/js/lib/smart_contract/wei_ether_converter.js new file mode 100644 index 0000000..29865a7 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/wei_ether_converter.js @@ -0,0 +1,28 @@ +import $ from 'jquery' +import { BigNumber } from 'bignumber.js' +import numeral from 'numeral' + +const weiToEtherConverter = (element, event) => { + const weiUnit = new BigNumber('1000000000000000000') + const $element = $(element) + const $conversionTextWei = $element.find('[data-conversion-text-wei]') + const $conversionTextEth = $element.find('[data-conversion-text-eth]') + const $conversionUnit = $element.find('[data-conversion-unit]') + const originalValueStr = $conversionUnit.data('original-value') + const unitVal = new BigNumber(numeral(originalValueStr).value()) + const weiVal = unitVal.dividedBy(weiUnit) + + if (event.target.checked) { + $conversionTextWei.removeClass('d-inline-block').addClass('d-none') + $conversionTextEth.removeClass('d-none').addClass('d-inline-block') + $conversionUnit.html(weiVal.toFixed() > 0 ? String(weiVal.toFixed()) : numeral(weiVal).format('0[.000000000000000000]')) + } else { + $conversionTextWei.removeClass('d-none').addClass('d-inline-block') + $conversionTextEth.removeClass('d-inline-block').addClass('d-none') + $conversionUnit.html(originalValueStr) + } +} + +$('[data-smart-contract-functions]').on('change', '[data-wei-ether-converter]', function (event) { + weiToEtherConverter(this, event) +}) diff --git a/apps/block_scout_web/assets/js/lib/stop_propagation.js b/apps/block_scout_web/assets/js/lib/stop_propagation.js new file mode 100644 index 0000000..c32fb6b --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/stop_propagation.js @@ -0,0 +1,3 @@ +import $ from 'jquery' + +$('[data-selector="stop-propagation"]').click((event) => event.stopPropagation()) diff --git a/apps/block_scout_web/assets/js/lib/text_ad.js b/apps/block_scout_web/assets/js/lib/text_ad.js new file mode 100644 index 0000000..94bd22a --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/text_ad.js @@ -0,0 +1,8 @@ +import $ from 'jquery' +import { showAd, fetchTextAdData } from './ad.js' + +$(function () { + if (showAd()) { + fetchTextAdData() + } +}) diff --git a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js new file mode 100644 index 0000000..1a64f54 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js @@ -0,0 +1,41 @@ +import $ from 'jquery' +import { formatAllUsdValues, formatUsdValue } from './currency' +import { TokenBalanceDropdownSearch } from './token_balance_dropdown_search' + +const tokenBalanceDropdown = (element) => { + const $element = $(element) + const $loading = $element.find('[data-loading]') + const $errorMessage = $element.find('[data-error-message]') + const apiPath = element.dataset.api_path + + $.get(apiPath) + .done(response => { + const responseHtml = formatAllUsdValues($(response)) + $element.html(responseHtml) + const tokensCount = $('[data-dropdown-token-balance-test]').length + const $addressTokenWorth = $('[data-test="address-tokens-worth"]') + const tokensDsName = (tokensCount > 1) ? ' tokens' : ' token' + $('[data-test="address-tokens-panel-tokens-worth"]').text(`${$addressTokenWorth.text()} | ${tokensCount} ${tokensDsName}`) + const $addressTokensPanelNativeWorth = $('[data-test="address-tokens-panel-native-worth"]') + const rawUsdValue = $addressTokensPanelNativeWorth.children('span').data('raw-usd-value') + const rawUsdTokensValue = $addressTokenWorth.data('usd-value') + const formattedFullUsdValue = formatUsdValue(parseFloat(rawUsdValue) + parseFloat(rawUsdTokensValue)) + $('[data-test="address-tokens-panel-net-worth"]').text(formattedFullUsdValue) + }) + .fail(() => { + $loading.hide() + $errorMessage.show() + }) +} + +export function loadTokenBalanceDropdown () { + $('[data-token-balance-dropdown]').each((_index, element) => tokenBalanceDropdown(element)) + + $('[data-token-balance-dropdown]').on('hidden.bs.dropdown', _event => { + $('[data-filter-dropdown-tokens]').val('').trigger('input') + }) + + $('[data-token-balance-dropdown]').on('input', function (event) { + TokenBalanceDropdownSearch(this, event) + }) +} diff --git a/apps/block_scout_web/assets/js/lib/token_balance_dropdown_search.js b/apps/block_scout_web/assets/js/lib/token_balance_dropdown_search.js new file mode 100644 index 0000000..3a98e30 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/token_balance_dropdown_search.js @@ -0,0 +1,43 @@ +import $ from 'jquery' + +const stringContains = (query, string) => { + return string.toLowerCase().search(query) === -1 +} + +const hideUnmatchedToken = (query, token) => { + const $token = $(token) + const tokenName = $token.data('token-name') + const tokenSymbol = $token.data('token-symbol') + + if (stringContains(query, tokenName) && stringContains(query, tokenSymbol)) { + $token.addClass('d-none') + } else { + $token.removeClass('d-none') + } +} + +const hideEmptyType = (container) => { + const $container = $(container) + const type = $container.data('token-type') + const countVisibleTokens = $container.children('[data-token-name]:not(.d-none)').length + + if (countVisibleTokens === 0) { + $container.addClass('d-none') + } else { + $(`[data-number-of-tokens-by-type='${type}']`).empty().append(countVisibleTokens) + $container.removeClass('d-none') + } +} + +export function TokenBalanceDropdownSearch (element, event) { + const $element = $(element) + const $tokensCount = $element.find('[data-tokens-count]') + const $tokens = $element.find('[data-token-name]') + const $tokenTypes = $element.find('[data-token-type]') + const query = event.target.value.toLowerCase() + + $tokens.each((_index, token) => hideUnmatchedToken(query, token)) + $tokenTypes.each((_index, container) => hideEmptyType(container)) + + $tokensCount.html($tokensCount.html().replace(/\d+/g, $tokens.not('.d-none').length)) +} diff --git a/apps/block_scout_web/assets/js/lib/token_icon.js b/apps/block_scout_web/assets/js/lib/token_icon.js new file mode 100644 index 0000000..6f283cb --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/token_icon.js @@ -0,0 +1,55 @@ +function getTokenIconUrl (chainID, addressHash) { + let chainName = null + switch (chainID) { + case '1': + chainName = 'ethereum' + break + case '99': + chainName = 'poa' + break + case '100': + chainName = 'xdai' + break + default: + chainName = null + break + } + if (chainName) { + return `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/${chainName}/assets/${addressHash}/logo.png` + } else { + return null + } +} + +function appendTokenIcon ($tokenIconContainer, chainID, addressHash, displayTokenIcons, size) { + const iconSize = size || 20 + const tokenIconURL = getTokenIconUrl(chainID.toString(), addressHash) + if (displayTokenIcons) { + checkLink(tokenIconURL) + .then(checkTokenIconLink => { + if (checkTokenIconLink) { + if ($tokenIconContainer) { + const img = new Image(iconSize, iconSize) + img.src = tokenIconURL + img.className = 'mr-1' + $tokenIconContainer.append(img) + } + } + }) + } +} + +async function checkLink (url) { + if (url) { + try { + const res = await fetch(url) + return res.ok + } catch (_error) { + return false + } + } else { + return false + } +} + +export { appendTokenIcon, checkLink, getTokenIconUrl } diff --git a/apps/block_scout_web/assets/js/lib/token_transfers_toggle.js b/apps/block_scout_web/assets/js/lib/token_transfers_toggle.js new file mode 100644 index 0000000..b2f029c --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/token_transfers_toggle.js @@ -0,0 +1,17 @@ +import $ from 'jquery' + +$(document.body).on('click', '[data-selector="token-transfer-open"]', event => { + const $tokenTransferOpen = event.target + const $parent = $tokenTransferOpen.parentElement + const $tokenTransferClose = $parent.querySelector("[data-selector='token-transfer-close']") + $tokenTransferOpen.classList.add('d-none') + $tokenTransferClose.classList.remove('d-none') +}) + +$(document.body).on('click', '[data-selector="token-transfer-close"]', event => { + const $tokenTransferClose = event.target + const $parent = $tokenTransferClose.parentElement + const $tokenTransferOpen = $parent.querySelector("[data-selector='token-transfer-open']") + $tokenTransferClose.classList.add('d-none') + $tokenTransferOpen.classList.remove('d-none') +}) diff --git a/apps/block_scout_web/assets/js/lib/tooltip.js b/apps/block_scout_web/assets/js/lib/tooltip.js new file mode 100644 index 0000000..0c29a92 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/tooltip.js @@ -0,0 +1,5 @@ +import $ from 'jquery' + +$(function () { + $('body').tooltip({ selector: '[data-toggle="tooltip"]' }) +}) diff --git a/apps/block_scout_web/assets/js/lib/transaction_input_dropdown.js b/apps/block_scout_web/assets/js/lib/transaction_input_dropdown.js new file mode 100644 index 0000000..75ed91f --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/transaction_input_dropdown.js @@ -0,0 +1,13 @@ +import $ from 'jquery' + +$('.tx-input-dropdown').click(function (e) { + e.preventDefault() + + const el = $(e.currentTarget) + const target = $(el.data('target')) + const targetToHide = $(el.data('target-to-hide')) + + target.show() + targetToHide.hide() + $('#tx-input-decoding-button').text(el.text()) +}) diff --git a/apps/block_scout_web/assets/js/lib/try_api.js b/apps/block_scout_web/assets/js/lib/try_api.js new file mode 100644 index 0000000..7f531c7 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/try_api.js @@ -0,0 +1,137 @@ +import $ from 'jquery' +import '../app' +import { escapeHtml } from './utils' + +// This file adds event handlers responsible for the 'Try it out' UI in the +// Etherscan-compatible API documentation page. + +function composeQuery (module, action, inputs) { + const parameters = queryParametersFromInputs(inputs) + return `?module=${module}&action=${action}` + parameters.join('') +} + +function queryParametersFromInputs (inputs) { + return $.map(inputs, queryParameterFromInput) +} + +function queryParameterFromInput (input) { + const key = $(input).attr('data-parameter-key') + const value = $(input).val() + + if (value === '') { + return '' + } else { + return `&${key}=${value}` + } +} + +function composeRequestUrl (query) { + const url = $('[data-endpoint-url]').attr('data-endpoint-url') + return `${url}${query}` +} + +function composeCurlCommand (requestUrl) { + return `curl -X GET "${requestUrl}" -H "accept: application/json"` +} + +function isResultVisible (module, action) { + return $(`[data-selector="${module}-${action}-try-api-ui-result"]`).is(':visible') +} + +function handleSuccess (query, xhr, clickedButton) { + const module = clickedButton.attr('data-module') + const action = clickedButton.attr('data-action') + const curl = $(`[data-selector="${module}-${action}-curl"]`)[0] + const requestUrl = $(`[data-selector="${module}-${action}-request-url"]`)[0] + const code = $(`[data-selector="${module}-${action}-server-response-code"]`)[0] + const body = $(`[data-selector="${module}-${action}-server-response-body"]`)[0] + const url = composeRequestUrl(escapeHtml(query)) + + curl.innerHTML = composeCurlCommand(url) + requestUrl.innerHTML = url + code.innerHTML = xhr.status + body.innerHTML = escapeHtml(JSON.stringify(xhr.responseJSON, undefined, 2)) + $(`[data-selector="${module}-${action}-try-api-ui-result"]`).show() + $(`[data-selector="${module}-${action}-btn-try-api-clear"]`).show() + clickedButton.html(clickedButton.data('original-text')) + clickedButton.prop('disabled', false) +} + +// Show 'Try it out' UI for a module/action. +$('button[data-selector*="btn-try-api"]').click(event => { + const clickedButton = $(event.target) + const module = clickedButton.attr('data-module') + const action = clickedButton.attr('data-action') + clickedButton.hide() + $(`button[data-selector="${module}-${action}-btn-try-api-cancel"]`).show() + $(`[data-selector="${module}-${action}-try-api-ui"]`).show() + + if (isResultVisible(module, action)) { + $(`[data-selector="${module}-${action}-btn-try-api-clear"]`).show() + } +}) + +// Hide 'Try it out' UI for a module/action. +$('button[data-selector*="btn-try-api-cancel"]').click(event => { + const clickedButton = $(event.target) + const module = clickedButton.attr('data-module') + const action = clickedButton.attr('data-action') + clickedButton.hide() + $(`[data-selector="${module}-${action}-try-api-ui"]`).hide() + $(`[data-selector="${module}-${action}-btn-try-api-clear"]`).hide() + $(`button[data-selector="${module}-${action}-btn-try-api"]`).show() +}) + +// Clear API server response/result, curl command, and request URL +$('button[data-selector*="btn-try-api-clear"]').click(event => { + const clickedButton = $(event.target) + const module = clickedButton.attr('data-module') + const action = clickedButton.attr('data-action') + clickedButton.hide() + $(`[data-selector="${module}-${action}-try-api-ui-result"]`).hide() +}) + +// Remove invalid class from required fields if not empty +$('input[data-selector*="try-api-ui"][data-required="true"]').on('keyup', (event) => { + if (event.target.value !== '') { + event.target.classList.remove('is-invalid') + } else { + event.target.classList.add('is-invalid') + } +}) + +// Execute API call +// +// Makes a request to the Explorer API with a given set of user defined +// parameters. The following related information is subsequently rendered below +// the execute button: +// +// * curl command +// * request URL +// * server response +// +$('button[data-try-api-ui-button-type="execute"]').click(event => { + const clickedButton = $(event.target) + const module = clickedButton.attr('data-module') + const action = clickedButton.attr('data-action') + const inputs = $(`input[data-selector="${module}-${action}-try-api-ui"]`) + const query = composeQuery(module, action, inputs) + const loadingText = ' Loading...' + + clickedButton.prop('disabled', true) + clickedButton.data('original-text', clickedButton.html()) + + if (clickedButton.html() !== loadingText) { + clickedButton.html(loadingText) + } + + $.ajax({ + url: composeRequestUrl(query), + success: (_data, _status, xhr) => { + handleSuccess(query, xhr, clickedButton) + }, + error: (xhr) => { + handleSuccess(query, xhr, clickedButton) + } + }) +}) diff --git a/apps/block_scout_web/assets/js/lib/try_eth_api.js b/apps/block_scout_web/assets/js/lib/try_eth_api.js new file mode 100644 index 0000000..3f00750 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/try_eth_api.js @@ -0,0 +1,82 @@ +import $ from 'jquery' +import './try_api' + +function composeCurlCommand (data) { + const url = $('[data-endpoint-url]').attr('data-endpoint-url') + return `curl -H "content-type: application/json" -X POST --data '${JSON.stringify(data)}' ${url}` +} + +function handleResponse (data, xhr, clickedButton) { + const module = clickedButton.attr('data-module') + const action = clickedButton.attr('data-action') + const curl = $(`[data-selector="${module}-${action}-curl"]`)[0] + const code = $(`[data-selector="${module}-${action}-server-response-code"]`)[0] + const body = $(`[data-selector="${module}-${action}-server-response-body"]`)[0] + + curl.innerHTML = composeCurlCommand(data) + code.innerHTML = xhr.status + body.innerHTML = JSON.stringify(xhr.responseJSON, undefined, 2) + $(`[data-selector="${module}-${action}-try-api-ui-result"]`).show() + $(`[data-selector="${module}-${action}-btn-try-api-clear"]`).show() + clickedButton.html(clickedButton.data('original-text')) + clickedButton.prop('disabled', false) +} + +function wrapJsonRpc (method, params) { + return { + id: 0, + jsonrpc: '2.0', + method, + params + } +} + +function parseInput (input) { + const type = $(input).attr('data-parameter-type') + const value = $(input).val() + + switch (type) { + case 'string': + return value + case 'json': + try { + return JSON.parse(value) + } catch (e) { + return {} + } + default: + return value + } +} + +function composeRequestUrl () { + const url = $('[data-endpoint-url]').attr('data-endpoint-url') + return url +} + +$('button[data-try-eth-api-ui-button-type="execute"]').click(event => { + const clickedButton = $(event.target) + const module = clickedButton.attr('data-module') + const action = clickedButton.attr('data-action') + const inputs = $(`input[data-selector="${module}-${action}-try-api-ui"]`) + const params = $.map(inputs, parseInput) + const formData = wrapJsonRpc(action, params) + const loadingText = ' Loading...' + + clickedButton.prop('disabled', true) + clickedButton.data('original-text', clickedButton.html()) + + if (clickedButton.html() !== loadingText) { + clickedButton.html(loadingText) + } + + $.ajax({ + url: composeRequestUrl(), + type: 'POST', + data: JSON.stringify(formData), + dataType: 'json', + contentType: 'application/json; charset=utf-8' + }) + .then((_data, _status, xhr) => handleResponse(formData, xhr, clickedButton)) + .fail((xhr) => handleResponse(formData, xhr, clickedButton)) +}) diff --git a/apps/block_scout_web/assets/js/lib/utils.js b/apps/block_scout_web/assets/js/lib/utils.js new file mode 100644 index 0000000..388813c --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/utils.js @@ -0,0 +1,38 @@ +import debounce from 'lodash.debounce' + +export function batchChannel (func) { + let msgs = [] + const debouncedFunc = debounce(() => { + func.apply(this, [msgs]) + msgs = [] + }, 1000, { maxWait: 5000 }) + return (msg) => { + msgs.push(msg) + debouncedFunc() + } +} + +export function showLoader (isTimeout, loader) { + if (isTimeout) { + const timeout = setTimeout(function () { + loader.removeAttr('hidden') + loader.show() + }, 100) + return timeout + } else { + loader.hide() + return null + } +} + +export function escapeHtml (text) { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + } + + return text.replace(/[&<>"']/g, function (m) { return map[m] }) +} diff --git a/apps/block_scout_web/assets/js/lib/validation.js b/apps/block_scout_web/assets/js/lib/validation.js new file mode 100644 index 0000000..a73511e --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/validation.js @@ -0,0 +1,64 @@ +import $ from 'jquery' + +export function setupValidation ($form, validators, $submit) { + const errors = {} + + updateSubmit($submit, errors) + + for (const [key, callback] of Object.entries(validators)) { + const $input = $form.find('[' + key + ']') + errors[key] = null + + $input + .ready(() => { + validateInput($input, callback, errors) + updateSubmit($submit, errors) + if (errors[key]) { + displayInputError($input, errors[key]) + } + }) + .blur(() => { + if (errors[key]) { + displayInputError($input, errors[key]) + } + }) + .on('input', () => { + hideInputError($input) + validateInput($input, callback, errors) + updateSubmit($submit, errors) + }) + } +} + +function validateInput ($input, callback, errors) { + if (!$input.val()) { + errors[$input.prop('id')] = null + return + } + + const validation = callback($input.val()) + if (validation === true) { + delete errors[$input.prop('id')] + return + } + + errors[$input.prop('id')] = validation +} + +function updateSubmit ($submit, errors) { + $submit.prop('disabled', !$.isEmptyObject(errors)) +} + +export function displayInputError ($input, message) { + const group = $input.parent('.input-group') + + group.addClass('input-status-error') + group.find('.input-group-message').html(message) +} + +export function hideInputError ($input) { + const group = $input.parent('.input-group') + + group.removeClass('input-status-error') + group.find('.input-group-message').html('') +} diff --git a/apps/block_scout_web/assets/js/locale.js b/apps/block_scout_web/assets/js/locale.js new file mode 100644 index 0000000..94f198c --- /dev/null +++ b/apps/block_scout_web/assets/js/locale.js @@ -0,0 +1,8 @@ +import moment from 'moment' +import numeral from 'numeral' +import 'numeral/locales' + +export const locale = 'en' + +moment.locale(locale) +numeral.locale(locale) 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 0000000..0e357e0 --- /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 new file mode 100644 index 0000000..47f1389 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -0,0 +1,321 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import URI from 'urijs' +import humps from 'humps' +import numeral from 'numeral' +import socket, { subscribeChannel } from '../socket' +import { createStore, connectElements } from '../lib/redux_helpers.js' +import { updateAllCalculatedUsdValues } from '../lib/currency.js' +import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown' +import '../lib/token_balance_dropdown_search' +import '../lib/async_listing_load' +import '../app' +import { + openQrModal +} from '../lib/modals' + +export const initialState = { + channelDisconnected: false, + + addressHash: null, + filter: null, + + balance: null, + balanceCard: null, + fetchedCoinBalanceBlockNumber: null, + transactionCount: null, + tokenTransferCount: null, + gasUsageCount: null, + validationCount: null, + countersFetched: false +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + if (state.beyondPageOne) return state + + return Object.assign({}, state, { + channelDisconnected: true + }) + } + case 'COUNTERS_FETCHED': { + return Object.assign({}, state, { + transactionCount: action.transactionCount, + tokenTransferCount: action.tokenTransferCount, + gasUsageCount: action.gasUsageCount, + validationCount: action.validationCount, + crcTotalWorth: action.crcTotalWorth, + countersFetched: true + }) + } + case 'RECEIVED_NEW_BLOCK': { + if (state.channelDisconnected) return state + + const validationCount = state.validationCount + 1 + return Object.assign({}, state, { validationCount }) + } + case 'RECEIVED_NEW_TRANSACTION': { + if (state.channelDisconnected) return state + + const transactionCount = (action.msg.fromAddressHash === state.addressHash) ? state.transactionCount + 1 : state.transactionCount + + return Object.assign({}, state, { transactionCount }) + } + case 'RECEIVED_NEW_TOKEN_TRANSFER': { + if (state.channelDisconnected) return state + + const tokenTransferCount = (action.msg.fromAddressHash === state.addressHash) ? state.tokenTransferCount + 1 : state.tokenTransferCount + + return Object.assign({}, state, { tokenTransferCount }) + } + case 'RECEIVED_UPDATED_BALANCE': { + return Object.assign({}, state, { + balanceCard: action.msg.balanceCard, + balance: parseFloat(action.msg.balance), + fetchedCoinBalanceBlockNumber: action.msg.fetchedCoinBalanceBlockNumber + }) + } + case 'RECEIVED_NEW_CURRENT_COIN_BALANCE': { + if (state.initialBlockNumber && action.msg.currentCoinBalanceBlockNumber < state.initialBlockNumber) return + return Object.assign({}, state, { + currentCoinBalance: action.msg.currentCoinBalanceHtml, + currentCoinBalanceBlockNumber: action.msg.currentCoinBalanceBlockNumberHtml, + initialBlockNumber: state.newBlockNumber, + newBlockNumber: action.msg.currentCoinBalanceBlockNumber + }) + } + default: + return state + } +} + +let fetchedTokenBalanceBlockNumber = 0 +function loadTokenBalance (blockNumber) { + if (blockNumber > fetchedTokenBalanceBlockNumber) { + fetchedTokenBalanceBlockNumber = blockNumber + setTimeout(loadTokenBalanceDropdown, 1000) + } else if (fetchedTokenBalanceBlockNumber === 0 && blockNumber === null) { + setTimeout(loadTokenBalanceDropdown, 1000) + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + }, + '[data-selector="balance-card"]': { + load ($el) { + return { balanceCard: $el.html(), balance: parseFloat($el.find('.current-balance-in-wei').attr('data-wei-value')) } + }, + render ($el, state, oldState) { + if (oldState.balance === state.balance || (isNaN(oldState.balance) && isNaN(state.balance))) return + $el.empty().append(state.balanceCard) + loadTokenBalance(state.fetchedCoinBalanceBlockNumber) + updateAllCalculatedUsdValues() + } + }, + '[data-selector="transaction-count"]': { + load ($el) { + return { transactionCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (state.countersFetched) { + if (oldState.transactionCount === state.transactionCount) return + const transactionsDSName = (state.transactionCount === 1) ? ' Transaction' : ' Transactions' + $el.empty().append(numeral(state.transactionCount).format() + transactionsDSName) + } + } + }, + '[data-selector="transfer-count"]': { + load ($el) { + return { tokenTransferCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (state.countersFetched) { + if (oldState.tokenTransferCount === state.tokenTransferCount) return + const transfersDSName = (state.tokenTransferCount === 1) ? ' Transfer' : ' Transfers' + $el.empty().append(numeral(state.tokenTransferCount).format() + transfersDSName) + } + } + }, + '[data-selector="gas-usage-count"]': { + load ($el) { + return { gasUsageCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (state.countersFetched) { + if (oldState.gasUsageCount === state.gasUsageCount) return + $el.empty().append(numeral(state.gasUsageCount).format()) + } + } + }, + '[data-selector="fetched-coin-balance-block-number"]': { + load ($el) { + return { fetchedCoinBalanceBlockNumber: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (oldState.fetchedCoinBalanceBlockNumber === state.fetchedCoinBalanceBlockNumber) return + $el.empty().append(numeral(state.fetchedCoinBalanceBlockNumber).format()) + } + }, + '[data-selector="validation-count"]': { + load ($el) { + return { validationCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (state.countersFetched && state.validationCount) { + if (oldState.validationCount === state.validationCount) return + $el.empty().append(numeral(state.validationCount).format()) + $('.address-validation-count-item').removeAttr('style') + } else { + $('.address-validation-count-item').css('display', 'none') + } + } + }, + '[data-test="address-tokens-panel-crc-total-worth"]': { + load ($el) { + return { countersFetched: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (state.countersFetched && state.crcTotalWorth) { + if (oldState.crcTotalWorth === state.crcTotalWorth) return + $el.empty().append(`${state.crcTotalWorth} CRC`) + if (state.crcTotalWorth !== '0') { + $('[data-test="address-tokens-panel-crc-total-worth-container"]').removeClass('d-none') + } else { + $('[data-test="address-tokens-panel-crc-total-worth-container"]').addClass('d-none') + } + } else { + $('[data-test="address-tokens-panel-crc-total-worth-container"]').addClass('d-none') + } + } + }, + '[data-selector="current-coin-balance"]': { + render ($el, state, oldState) { + 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 + $el.empty().append(state.currentCoinBalanceBlockNumber) + } + }, + '[data-last-balance-update]': { + load ($el) { + return { initialBlockNumber: numeral($el.data('last-balance-update')).value() } + } + } +} + +function loadCounters (store) { + const $element = $('[data-async-counters]') + const path = $element.data().asyncCounters + + function fetchCounters () { + $.getJSON(path) + .done(response => store.dispatch(Object.assign({ type: 'COUNTERS_FETCHED' }, humps.camelizeKeys(response)))) + } + + fetchCounters() +} + +const $addressDetailsPage = $('[data-page="address-details"]') +if ($addressDetailsPage.length) { + const pathParts = window.location.pathname.split('/') + const shouldScroll = pathParts.includes('transactions') || + pathParts.includes('token-transfers') || + pathParts.includes('tokens') || + pathParts.includes('internal-transactions') || + pathParts.includes('coin-balances') || + pathParts.includes('logs') || + pathParts.includes('validations') || + pathParts.includes('contracts') || + pathParts.includes('decompiled-contracts') || + pathParts.includes('read-contract') || + pathParts.includes('read-proxy') || + pathParts.includes('write-contract') || + pathParts.includes('write-proxy') + + if (shouldScroll) { + location.href = '#address-tabs' + } + + window.onbeforeunload = () => { + window.loading = true + } + + const store = createStore(reducer) + const addressHash = $addressDetailsPage[0].dataset.pageAddressHash + const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true)) + store.dispatch({ + type: 'PAGE_LOAD', + addressHash, + filter, + beyondPageOne: !!blockNumber + }) + connectElements({ store, elements }) + + const addressChannel = subscribeChannel(`addresses:${addressHash}`) + + addressChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + addressChannel.on('balance', (msg) => store.dispatch({ + type: 'RECEIVED_UPDATED_BALANCE', + msg: humps.camelizeKeys(msg) + })) + addressChannel.on('token_balance', (msg) => loadTokenBalance( + msg.block_number + )) + addressChannel.on('transaction', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_TRANSACTION', + msg: humps.camelizeKeys(msg) + }) + }) + addressChannel.on('transfer', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_TOKEN_TRANSFER', + msg: humps.camelizeKeys(msg) + }) + }) + addressChannel.on('current_coin_balance', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_CURRENT_COIN_BALANCE', + msg: humps.camelizeKeys(msg) + }) + }) + + const blocksChannel = socket.channel(`blocks:${addressHash}`, {}) + blocksChannel.join() + blocksChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + blocksChannel.on('new_block', (msg) => store.dispatch({ + type: 'RECEIVED_NEW_BLOCK', + 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) + + $('.btn-qr-icon').click(_event => { + openQrModal() + }) +} diff --git a/apps/block_scout_web/assets/js/pages/address/coin_balances.js b/apps/block_scout_web/assets/js/pages/address/coin_balances.js new file mode 100644 index 0000000..f85ade9 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/coin_balances.js @@ -0,0 +1,68 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import socket from '../../socket' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../../lib/async_listing_load' +import '../address' + +export const initialState = { + channelDisconnected: false +} + +export function reducer (state, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + if (state.beyondPageOne) return state + + return Object.assign({}, state, { + channelDisconnected: true + }) + } + case 'RECEIVED_NEW_COIN_BALANCE': { + if (state.channelDisconnected || state.beyondPageOne) return state + + return Object.assign({}, state, { + items: [action.msg.coinBalanceHtml, ...state.items] + }) + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + } +} + +if ($('[data-page="coin-balance-history"]').length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.blockNumber') + const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash + + store.dispatch({ type: 'PAGE_LOAD', addressHash }) + connectElements({ store, elements }) + + const addressChannel = socket.channel(`addresses:${addressHash}`, {}) + addressChannel.join() + addressChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + addressChannel.on('coin_balance', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_COIN_BALANCE', + msg: humps.camelizeKeys(msg) + }) + }) +} diff --git a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js new file mode 100644 index 0000000..5380f12 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js @@ -0,0 +1,117 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import numeral from 'numeral' +import socket from '../../socket' +import { batchChannel } from '../../lib/utils' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../../lib/async_listing_load' +import '../address' +import { isFiltered } from './utils' + +const BATCH_THRESHOLD = 10 + +export const initialState = { + channelDisconnected: false, + addressHash: null, + filter: null, + internalTransactionsBatch: [] +} + +export function reducer (state, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + if (state.beyondPageOne) return state + + return Object.assign({}, state, { + channelDisconnected: true, + internalTransactionsBatch: [] + }) + } + case 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH': { + if (state.channelDisconnected || state.beyondPageOne) return state + + const incomingInternalTransactions = action.msgs + .filter(({ toAddressHash, fromAddressHash }) => ( + !state.filter || + (state.filter === 'to' && toAddressHash === state.addressHash) || + (state.filter === 'from' && fromAddressHash === state.addressHash) + )).map(msg => msg.internalTransactionHtml) + + if (!state.internalTransactionsBatch.length && incomingInternalTransactions.length < BATCH_THRESHOLD) { + return Object.assign({}, state, { + items: [ + ...incomingInternalTransactions.reverse(), + ...state.items + ] + }) + } else { + return Object.assign({}, state, { + internalTransactionsBatch: [ + ...incomingInternalTransactions.reverse(), + ...state.internalTransactionsBatch + ] + }) + } + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + }, + '[data-selector="channel-batching-count"]': { + render ($el, state) { + const $channelBatching = $('[data-selector="channel-batching-message"]') + if (!state.internalTransactionsBatch.length) return $channelBatching.hide() + $channelBatching.show() + $el[0].innerHTML = numeral(state.internalTransactionsBatch.length).format() + } + }, + '[data-test="filter_dropdown"]': { + render ($el, state) { + if (state.emptyResponse && !state.isSearch) { + if (isFiltered(state.filter)) { + $el.addClass('no-rm') + } else { + return $el.hide() + } + } else { + $el.removeClass('no-rm') + } + + return $el.show() + } + } +} + +if ($('[data-page="address-internal-transactions"]').length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.key') + const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash + + store.dispatch({ type: 'PAGE_LOAD', addressHash }) + connectElements({ store, elements }) + + const addressChannel = socket.channel(`addresses:${addressHash}`, {}) + addressChannel.join() + addressChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + addressChannel.on('internal_transaction', batchChannel((msgs) => store.dispatch({ + type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', + msgs: humps.camelizeKeys(msgs) + }))) +} diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js new file mode 100644 index 0000000..fe99a6e --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/logs.js @@ -0,0 +1,104 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore, loadPage } from '../../lib/async_listing_load' +import '../address' +import { utils } from 'web3' + +export const initialState = { + addressHash: null, + isSearch: false +} + +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 + } +} + +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() + } + } +} + +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 }) + + const searchFunc = (_event) => { + store.dispatch({ + type: 'START_SEARCH', + addressHash + }) + 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}` + loadPage(store, path) + } + + store.dispatch({ + type: 'PAGE_LOAD', + addressHash + }) + + $element.on('click', '[data-search-button]', searchFunc) + + $element.on('click', '[data-cancel-search-button]', (_event) => { + $('[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/address/token_transfers.js b/apps/block_scout_web/assets/js/pages/address/token_transfers.js new file mode 100644 index 0000000..b09e32f --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/token_transfers.js @@ -0,0 +1,107 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import URI from 'urijs' +import humps from 'humps' +import { subscribeChannel } from '../../socket' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../../lib/async_listing_load' +import '../address' +import { isFiltered } from './utils' + +export const initialState = { + addressHash: null, + channelDisconnected: false, + filter: null +} + +export function reducer (state, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + if (state.beyondPageOne) return state + + return Object.assign({}, state, { channelDisconnected: true }) + } + case 'RECEIVED_NEW_TOKEN_TRANSFER': { + if (state.channelDisconnected) return state + + if (state.beyondPageOne || + (state.filter === 'to' && action.msg.toAddressHash !== state.addressHash) || + (state.filter === 'from' && action.msg.fromAddressHash !== state.addressHash)) { + return state + } + + return Object.assign({}, state, { items: [action.msg.tokenTransferHtml, ...state.items] }) + } + case 'RECEIVED_NEW_REWARD': { + if (state.channelDisconnected) return state + + return Object.assign({}, state, { items: [action.msg.rewardHtml, ...state.items] }) + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + }, + '[data-test="filter_dropdown"]': { + render ($el, state) { + if (state.emptyResponse && !state.isSearch) { + if (isFiltered(state.filter)) { + $el.addClass('no-rm') + } else { + return $el.hide() + } + } else { + $el.removeClass('no-rm') + } + + return $el.show() + } + } +} + +if ($('[data-page="address-token-transfers"]').length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash + const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true)) + + connectElements({ store, elements }) + + store.dispatch({ + type: 'PAGE_LOAD', + addressHash, + filter, + beyondPageOne: !!blockNumber + }) + + const addressChannel = subscribeChannel(`addresses:${addressHash}`) + addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + addressChannel.on('token_transfer', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_TOKEN_TRANSFER', + msg: humps.camelizeKeys(msg) + }) + }) + + const rewardsChannel = subscribeChannel(`rewards:${addressHash}`) + rewardsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + rewardsChannel.on('new_reward', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_REWARD', + msg: humps.camelizeKeys(msg) + }) + }) +} diff --git a/apps/block_scout_web/assets/js/pages/address/transactions.js b/apps/block_scout_web/assets/js/pages/address/transactions.js new file mode 100644 index 0000000..9343d0c --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/transactions.js @@ -0,0 +1,181 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import URI from 'urijs' +import humps from 'humps' +import numeral from 'numeral' +import { subscribeChannel } from '../../socket' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../../lib/async_listing_load' +import { batchChannel } from '../../lib/utils' +import '../address' +import { isFiltered } from './utils' + +const BATCH_THRESHOLD = 6 + +export const initialState = { + addressHash: null, + channelDisconnected: false, + filter: null, + transactionsBatch: [] +} + +export function reducer (state, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + if (state.beyondPageOne) return state + + return Object.assign({}, state, { channelDisconnected: true }) + } + case 'RECEIVED_NEW_TRANSACTION': { + if (state.channelDisconnected) return state + + if (state.beyondPageOne || + (state.filter === 'to' && action.msg.toAddressHash !== state.addressHash) || + (state.filter === 'from' && action.msg.fromAddressHash !== state.addressHash)) { + return state + } + + return Object.assign({}, state, { items: [action.msg.transactionHtml, ...state.items] }) + } + case 'RECEIVED_NEW_TRANSACTION_BATCH': { + if (state.channelDisconnected || state.beyondPageOne) return state + + const transactionCount = state.transactionCount + action.msgs.length + + if (!state.transactionsBatch.length && action.msgs.length < BATCH_THRESHOLD) { + return Object.assign({}, state, { + items: [ + ...action.msgs.map(msg => msg.transactionHtml).reverse(), + ...state.items + ], + transactionCount + }) + } else { + return Object.assign({}, state, { + transactionsBatch: [ + ...action.msgs.reverse(), + ...state.transactionsBatch + ], + transactionCount + }) + } + } + case 'RECEIVED_NEW_REWARD': { + if (state.channelDisconnected) return state + + return Object.assign({}, state, { items: [action.msg.rewardHtml, ...state.items] }) + } + case 'TRANSACTION_BATCH_EXPANDED': { + return Object.assign({}, state, { + transactionsBatch: [] + }) + } + case 'TRANSACTIONS_FETCHED': + return Object.assign({}, state, { items: [...action.msg.items] }) + case 'TRANSACTIONS_FETCH_ERROR': { + const $channelBatching = $('[data-selector="channel-batching-message"]') + $channelBatching.show() + return state + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + }, + '[data-test="filter_dropdown"]': { + render ($el, state) { + if (state.emptyResponse && !state.isSearch) { + if (isFiltered(state.filter)) { + $el.addClass('no-rm') + } else { + return $el.hide() + } + } else { + $el.removeClass('no-rm') + } + + return $el.show() + } + }, + '[data-selector="channel-batching-count"]': { + render ($el, state, _oldState) { + const $channelBatching = $('[data-selector="channel-batching-message"]') + if (!state.transactionsBatch.length) return $channelBatching.hide() + $channelBatching.show() + $el[0].innerHTML = numeral(state.transactionsBatch.length).format() + } + } +} + +if ($('[data-page="address-transactions"]').length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash + const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true)) + + connectElements({ store, elements }) + + store.dispatch({ + type: 'PAGE_LOAD', + addressHash, + filter, + beyondPageOne: !!blockNumber + }) + + const addressChannel = subscribeChannel(`addresses:${addressHash}`) + addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + addressChannel.on('transaction', batchChannel((msgs) => + store.dispatch({ + type: 'RECEIVED_NEW_TRANSACTION_BATCH', + msgs: humps.camelizeKeys(msgs) + }) + )) + addressChannel.on('pending_transaction', batchChannel((msgs) => + store.dispatch({ + type: 'RECEIVED_NEW_TRANSACTION_BATCH', + msgs: humps.camelizeKeys(msgs) + }) + )) + + const rewardsChannel = subscribeChannel(`rewards:${addressHash}`) + rewardsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + rewardsChannel.on('new_reward', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_REWARD', + msg: humps.camelizeKeys(msg) + }) + }) + + const $txReloadButton = $('[data-selector="reload-transactions-button"]') + const $channelBatching = $('[data-selector="channel-batching-message"]') + $txReloadButton.on('click', (event) => { + event.preventDefault() + loadTransactions(store) + $channelBatching.hide() + store.dispatch({ + type: 'TRANSACTION_BATCH_EXPANDED' + }) + }) +} + +function loadTransactions (store) { + const path = $('[class="card-body"]')[0].dataset.asyncListing + store.dispatch({ type: 'START_TRANSACTIONS_FETCH' }) + $.getJSON(path, { type: 'JSON' }) + .done(response => store.dispatch({ type: 'TRANSACTIONS_FETCHED', msg: humps.camelizeKeys(response) })) + .fail(() => store.dispatch({ type: 'TRANSACTIONS_FETCH_ERROR' })) + .always(() => store.dispatch({ type: 'FINISH_TRANSACTIONS_FETCH' })) +} diff --git a/apps/block_scout_web/assets/js/pages/address/utils.js b/apps/block_scout_web/assets/js/pages/address/utils.js new file mode 100644 index 0000000..1e00dfa --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/utils.js @@ -0,0 +1,5 @@ +function isFiltered (filter) { + return (filter === 'to' || filter === 'from') +} + +export { isFiltered } diff --git a/apps/block_scout_web/assets/js/pages/address/validations.js b/apps/block_scout_web/assets/js/pages/address/validations.js new file mode 100644 index 0000000..1289239 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/validations.js @@ -0,0 +1,69 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import socket from '../../socket' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../../lib/async_listing_load.js' +import '../address' + +export const initialState = { + addressHash: null, + channelDisconnected: false +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + return Object.assign({}, state, { channelDisconnected: true }) + } + case 'RECEIVED_NEW_BLOCK': { + if (state.channelDisconnected) return state + if (state.beyondPageOne) return state + + return Object.assign({}, state, { + items: [ + action.blockHtml, + ...state.items + ] + }) + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + } +} + +if ($('[data-page="blocks-validated"]').length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.blockNumber') + connectElements({ store, elements }) + const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash + store.dispatch({ + type: 'PAGE_LOAD', + addressHash + }) + + const blocksChannel = socket.channel(`blocks:${addressHash}`, {}) + blocksChannel.join() + blocksChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + blocksChannel.on('new_block', (msg) => store.dispatch({ + type: 'RECEIVED_NEW_BLOCK', + blockHtml: humps.camelizeKeys(msg).blockHtml + })) +} diff --git a/apps/block_scout_web/assets/js/pages/admin/tasks.js b/apps/block_scout_web/assets/js/pages/admin/tasks.js new file mode 100644 index 0000000..146333a --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/admin/tasks.js @@ -0,0 +1,27 @@ +import $ from 'jquery' +import '../../app' + +const runTask = (event) => { + const element = event.currentTarget + const $element = $(element) + const $loading = $element.find('[data-loading-message]') + const $errorMessage = $element.find('[data-error-message]') + const $successMessage = $element.find('[data-success-message]') + const apiPath = element.dataset.api_path + + $errorMessage.hide() + $successMessage.hide() + $loading.show() + + $.get(apiPath) + .done(_response => { + $successMessage.show() + $loading.hide() + }) + .fail(() => { + $loading.hide() + $errorMessage.show() + }) +} + +$('#run-create-contract-methods').click(runTask) diff --git a/apps/block_scout_web/assets/js/pages/blocks.js b/apps/block_scout_web/assets/js/pages/blocks.js new file mode 100644 index 0000000..734b9d9 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/blocks.js @@ -0,0 +1,129 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import last from 'lodash.last' +import min from 'lodash.min' +import max from 'lodash.max' +import keys from 'lodash.keys' +import rangeRight from 'lodash.rangeright' +import humps from 'humps' +import socket from '../socket' +import { connectElements } from '../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../lib/async_listing_load' +import '../app' + +export const initialState = { + channelDisconnected: false +} + +export const blockReducer = withMissingBlocks(baseReducer) + +function baseReducer (state = initialState, action) { + switch (action.type) { + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + return Object.assign({}, state, { + channelDisconnected: true + }) + } + case 'RECEIVED_NEW_BLOCK': { + if (state.channelDisconnected || state.beyondPageOne || state.blockType !== 'block') return state + + const blockNumber = getBlockNumber(action.msg.blockHtml) + const minBlock = getBlockNumber(last(state.items)) + + if (state.items.length && blockNumber < minBlock) return state + + return Object.assign({}, state, { + items: [action.msg.blockHtml, ...state.items] + }) + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + } +} + +function getBlockNumber (blockHtml) { + return $(blockHtml).data('blockNumber') +} + +function withMissingBlocks (reducer) { + return (...args) => { + const result = reducer(...args) + + if (result.items.length < 2) return result + + const blockNumbersToItems = result.items.reduce((acc, item) => { + const blockNumber = getBlockNumber(item) + acc[blockNumber] = acc[blockNumber] || item + return acc + }, {}) + + 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) + .map((blockNumber) => blockNumbersToItems[blockNumber] || placeHolderBlock(blockNumber)) + }) + } +} + +const $blockListPage = $('[data-page="block-list"]') +const $uncleListPage = $('[data-page="uncle-list"]') +const $reorgListPage = $('[data-page="reorg-list"]') +if ($blockListPage.length || $uncleListPage.length || $reorgListPage.length) { + window.onbeforeunload = () => { + window.loading = true + } + + const blockType = $blockListPage.length ? 'block' : $uncleListPage.length ? 'uncle' : 'reorg' + + const store = createAsyncLoadStore( + $blockListPage.length ? blockReducer : baseReducer, + Object.assign({}, initialState, { blockType }), + 'dataset.blockNumber' + ) + connectElements({ store, elements }) + + const blocksChannel = socket.channel('blocks:new_block', {}) + blocksChannel.join() + blocksChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + blocksChannel.on('new_block', (msg) => store.dispatch({ + type: 'RECEIVED_NEW_BLOCK', + msg: humps.camelizeKeys(msg) + })) +} + +export function placeHolderBlock (blockNumber) { + return ` +
    +
    + + + + +
    + ${blockNumber} +
    ${window.localized['Block Processing']}
    +
    +
    +
    + ` +} diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js new file mode 100644 index 0000000..7d8b64f --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/chain.js @@ -0,0 +1,412 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import first from 'lodash.first' +import rangeRight from 'lodash.rangeright' +import find from 'lodash.find' +import map from 'lodash.map' +import humps from 'humps' +import numeral from 'numeral' +import socket from '../socket' +import { updateAllCalculatedUsdValues, formatUsdValue } from '../lib/currency' +import { createStore, connectElements } from '../lib/redux_helpers.js' +import { batchChannel, showLoader } from '../lib/utils' +import listMorph from '../lib/list_morph' +import '../app' + +const BATCH_THRESHOLD = 6 +const BLOCKS_PER_PAGE = 4 + +export const initialState = { + addressCount: null, + availableSupply: null, + averageBlockTime: null, + marketHistoryData: null, + blocks: [], + blocksLoading: true, + blocksError: false, + transactions: [], + transactionsBatch: [], + transactionsError: false, + transactionsLoading: true, + transactionCount: null, + totalGasUsageCount: null, + usdMarketCap: null, + blockCount: null +} + +export const reducer = withMissingBlocks(baseReducer) + +function baseReducer (state = initialState, action) { + switch (action.type) { + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'RECEIVED_NEW_ADDRESS_COUNT': { + return Object.assign({}, state, { + addressCount: action.msg.count + }) + } + case 'RECEIVED_NEW_BLOCK': { + if (!state.blocks.length || state.blocks[0].blockNumber < action.msg.blockNumber) { + let pastBlocks + if (state.blocks.length < BLOCKS_PER_PAGE) { + pastBlocks = state.blocks + } else { + $('.miner-address-tooltip').tooltip('hide') + pastBlocks = state.blocks.slice(0, -1) + } + return Object.assign({}, state, { + averageBlockTime: action.msg.averageBlockTime, + blocks: [ + action.msg, + ...pastBlocks + ], + blockCount: action.msg.blockNumber + 1 + }) + } else { + return Object.assign({}, state, { + blocks: state.blocks.map((block) => block.blockNumber === action.msg.blockNumber ? action.msg : block), + blockCount: action.msg.blockNumber + 1 + }) + } + } + case 'START_BLOCKS_FETCH': { + return Object.assign({}, state, { blocksError: false, blocksLoading: true }) + } + case 'BLOCKS_FINISH_REQUEST': { + return Object.assign({}, state, { blocksLoading: false }) + } + case 'BLOCKS_FETCHED': { + return Object.assign({}, state, { blocks: [...action.msg.blocks], blocksLoading: false }) + } + case 'BLOCKS_REQUEST_ERROR': { + return Object.assign({}, state, { blocksError: true, blocksLoading: false }) + } + case 'RECEIVED_NEW_EXCHANGE_RATE': { + return Object.assign({}, state, { + availableSupply: action.msg.exchangeRate.availableSupply, + marketHistoryData: action.msg.marketHistoryData, + usdMarketCap: action.msg.exchangeRate.marketCapUsd + }) + } + case 'RECEIVED_NEW_TRANSACTION_BATCH': { + if (state.channelDisconnected) return state + + const transactionCount = state.transactionCount + action.msgs.length + + if (state.transactionsLoading || state.transactionsError) { + return Object.assign({}, state, { transactionCount }) + } + + const transactionsLength = state.transactions.length + action.msgs.length + if (transactionsLength < BATCH_THRESHOLD) { + return Object.assign({}, state, { + transactions: [ + ...action.msgs.reverse(), + ...state.transactions + ], + transactionCount + }) + } else if (!state.transactionsBatch.length && action.msgs.length < BATCH_THRESHOLD) { + return Object.assign({}, state, { + transactions: [ + ...action.msgs.reverse(), + ...state.transactions.slice(0, -1 * action.msgs.length) + ], + transactionCount + }) + } else { + return Object.assign({}, state, { + transactionsBatch: [ + ...action.msgs.reverse(), + ...state.transactionsBatch + ], + transactionCount + }) + } + } + case 'TRANSACTION_BATCH_EXPANDED': { + return Object.assign({}, state, { + transactionsBatch: [] + }) + } + case 'RECEIVED_UPDATED_TRANSACTION_STATS': { + return Object.assign({}, state, { + transactionStats: action.msg.stats + }) + } + case 'START_TRANSACTIONS_FETCH': + return Object.assign({}, state, { transactionsError: false, transactionsLoading: true }) + case 'TRANSACTIONS_FETCHED': + return Object.assign({}, state, { transactions: [...action.msg.transactions] }) + case 'TRANSACTIONS_FETCH_ERROR': + return Object.assign({}, state, { transactionsError: true }) + case 'FINISH_TRANSACTIONS_FETCH': + return Object.assign({}, state, { transactionsLoading: false }) + default: + return state + } +} + +function withMissingBlocks (reducer) { + return (...args) => { + const result = reducer(...args) + + if (!result.blocks || result.blocks.length < 2) return result + + const maxBlock = first(result.blocks).blockNumber + const minBlock = maxBlock - (result.blocks.length - 1) + + return Object.assign({}, result, { + blocks: rangeRight(minBlock, maxBlock + 1) + .map((blockNumber) => find(result.blocks, ['blockNumber', blockNumber]) || { + blockNumber, + chainBlockHtml: placeHolderBlock(blockNumber) + }) + }) + } +} + +let chart +const elements = { + '[data-chart="historyChart"]': { + load () { + chart = window.dashboardChart + }, + render (_$el, state, oldState) { + if (!chart || (oldState.availableSupply === state.availableSupply && oldState.marketHistoryData === state.marketHistoryData) || !state.availableSupply) return + + chart.updateMarketHistory(state.availableSupply, state.marketHistoryData) + + if (!chart || (JSON.stringify(oldState.transactionStats) === JSON.stringify(state.transactionStats))) return + + chart.updateTransactionHistory(state.transactionStats) + } + }, + '[data-selector="transaction-count"]': { + load ($el) { + return { transactionCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (oldState.transactionCount === state.transactionCount) return + $el.empty().append(numeral(state.transactionCount).format()) + } + }, + '[data-selector="total-gas-usage"]': { + load ($el) { + return { totalGasUsageCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (oldState.totalGasUsageCount === state.totalGasUsageCount) return + $el.empty().append(numeral(state.totalGasUsageCount).format()) + } + }, + '[data-selector="block-count"]': { + load ($el) { + return { blockCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (oldState.blockCount === state.blockCount) return + $el.empty().append(numeral(state.blockCount).format()) + } + }, + '[data-selector="address-count"]': { + render ($el, state, oldState) { + if (oldState.addressCount === state.addressCount) return + $el.empty().append(state.addressCount) + } + }, + '[data-selector="average-block-time"]': { + render ($el, state, oldState) { + if (oldState.averageBlockTime === state.averageBlockTime) return + $el.empty().append(state.averageBlockTime) + } + }, + '[data-selector="market-cap"]': { + render ($el, state, oldState) { + if (oldState.usdMarketCap === state.usdMarketCap) return + $el.empty().append(formatUsdValue(state.usdMarketCap)) + } + }, + '[data-selector="tx_per_day"]': { + render ($el, state, oldState) { + if (!(JSON.stringify(oldState.transactionStats) === JSON.stringify(state.transactionStats))) { + $el.empty().append(numeral(state.transactionStats[0].number_of_transactions).format('0,0')) + } + } + }, + '[data-selector="chain-block-list"]': { + load ($el) { + return { + blocksPath: $el[0].dataset.url + } + }, + render ($el, state, oldState) { + if (oldState.blocks === state.blocks) return + + const container = $el[0] + + if (state.blocksLoading === false) { + const blocks = map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0]) + listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true }) + } + } + }, + '[data-selector="chain-block-list"] [data-selector="error-message"]': { + render ($el, state, _oldState) { + if (state.blocksError) { + $el.show() + } else { + $el.hide() + } + } + }, + '[data-selector="chain-block-list"] [data-selector="loading-message"]': { + render ($el, state, _oldState) { + showLoader(state.blocksLoading, $el) + } + }, + '[data-selector="transactions-list"] [data-selector="error-message"]': { + render ($el, state, _oldState) { + $el.toggle(state.transactionsError) + } + }, + '[data-selector="transactions-list"] [data-selector="loading-message"]': { + render ($el, state, _oldState) { + showLoader(state.transactionsLoading, $el) + } + }, + '[data-selector="transactions-list"]': { + load ($el) { + return { transactionsPath: $el[0].dataset.transactionsPath } + }, + render ($el, state, oldState) { + if (oldState.transactions === state.transactions) return + const container = $el[0] + const newElements = map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0]) + listMorph(container, newElements, { key: 'dataset.identifierHash' }) + } + }, + '[data-selector="channel-batching-count"]': { + render ($el, state, _oldState) { + const $channelBatching = $('[data-selector="channel-batching-message"]') + if (!state.transactionsBatch.length) return $channelBatching.hide() + $channelBatching.show() + $el[0].innerHTML = numeral(state.transactionsBatch.length).format() + } + } +} + +const $chainDetailsPage = $('[data-page="chain-details"]') +if ($chainDetailsPage.length) { + const store = createStore(reducer) + connectElements({ store, elements }) + + loadTransactions(store) + bindTransactionErrorMessage(store) + + loadBlocks(store) + bindBlockErrorMessage(store) + + const exchangeRateChannel = socket.channel('exchange_rate:new_rate') + exchangeRateChannel.join() + exchangeRateChannel.on('new_rate', (msg) => { + updateAllCalculatedUsdValues(humps.camelizeKeys(msg).exchangeRate.usdValue) + store.dispatch({ + type: 'RECEIVED_NEW_EXCHANGE_RATE', + msg: humps.camelizeKeys(msg) + }) + }) + + const addressesChannel = socket.channel('addresses:new_address') + addressesChannel.join() + addressesChannel.on('count', msg => store.dispatch({ + type: 'RECEIVED_NEW_ADDRESS_COUNT', + msg: humps.camelizeKeys(msg) + })) + + const blocksChannel = socket.channel('blocks:new_block') + blocksChannel.join() + blocksChannel.on('new_block', msg => store.dispatch({ + type: 'RECEIVED_NEW_BLOCK', + msg: humps.camelizeKeys(msg) + })) + + const transactionsChannel = socket.channel('transactions:new_transaction') + transactionsChannel.join() + transactionsChannel.on('transaction', batchChannel((msgs) => store.dispatch({ + type: 'RECEIVED_NEW_TRANSACTION_BATCH', + msgs: humps.camelizeKeys(msgs) + }))) + + const transactionStatsChannel = socket.channel('transactions:stats') + transactionStatsChannel.join() + transactionStatsChannel.on('update', msg => store.dispatch({ + type: 'RECEIVED_UPDATED_TRANSACTION_STATS', + msg + })) + + const $txReloadButton = $('[data-selector="reload-transactions-button"]') + const $channelBatching = $('[data-selector="channel-batching-message"]') + $txReloadButton.on('click', (event) => { + event.preventDefault() + loadTransactions(store) + $channelBatching.hide() + store.dispatch({ + type: 'TRANSACTION_BATCH_EXPANDED' + }) + }) +} + +function loadTransactions (store) { + const path = store.getState().transactionsPath + store.dispatch({ type: 'START_TRANSACTIONS_FETCH' }) + $.getJSON(path) + .done(response => store.dispatch({ type: 'TRANSACTIONS_FETCHED', msg: humps.camelizeKeys(response) })) + .fail(() => store.dispatch({ type: 'TRANSACTIONS_FETCH_ERROR' })) + .always(() => store.dispatch({ type: 'FINISH_TRANSACTIONS_FETCH' })) +} + +function bindTransactionErrorMessage (store) { + $('[data-selector="transactions-list"] [data-selector="error-message"]').on('click', _event => loadTransactions(store)) +} + +export function placeHolderBlock (blockNumber) { + return ` +
    +
    + + + + +
    + ${blockNumber} +
    ${window.localized['Block Processing']}
    +
    +
    +
    + ` +} + +function loadBlocks (store) { + const url = store.getState().blocksPath + + store.dispatch({ type: 'START_BLOCKS_FETCH' }) + + $.getJSON(url) + .done(response => { + store.dispatch({ type: 'BLOCKS_FETCHED', msg: humps.camelizeKeys(response) }) + }) + .fail(() => store.dispatch({ type: 'BLOCKS_REQUEST_ERROR' })) + .always(() => store.dispatch({ type: 'BLOCKS_FINISH_REQUEST' })) +} + +function bindBlockErrorMessage (store) { + $('[data-selector="chain-block-list"] [data-selector="error-message"]').on('click', _event => loadBlocks(store)) +} 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 new file mode 100644 index 0000000..e9e0bdc --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js @@ -0,0 +1,11 @@ +import $ from 'jquery' + +$('.dark-mode-changer').click(function () { + if (localStorage.getItem('current-color-mode') === 'dark') { + localStorage.setItem('current-color-mode', 'light') + } else { + localStorage.setItem('current-color-mode', 'dark') + } + // reload each theme switch + document.location.reload(true) +}) diff --git a/apps/block_scout_web/assets/js/pages/layout.js b/apps/block_scout_web/assets/js/pages/layout.js new file mode 100644 index 0000000..43fb5a2 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/layout.js @@ -0,0 +1,56 @@ +import $ from 'jquery' +import { addChainToMM } from '../lib/add_chain_to_mm' + +$(document).click(function (event) { + const clickover = $(event.target) + const _opened = $('.navbar-collapse').hasClass('show') + if (_opened === true && $('.navbar').find(clickover).length < 1) { + $('.navbar-toggler').click() + } +}) + +const search = (value) => { + if (value) { + window.location.href = `/search?q=${value}` + } +} + +$(document) + .on('keyup', function (event) { + if (event.key === '/') { + $('.main-search-autocomplete').trigger('focus') + } + }) + .on('click', '.js-btn-add-chain-to-mm', event => { + const $btn = $(event.target) + addChainToMM({ btn: $btn }) + }) + +$('.main-search-autocomplete').on('keyup', function (event) { + if (event.key === 'Enter') { + let selected = false + $('li[id^="autoComplete_result_"]').each(function () { + if ($(this).attr('aria-selected')) { + selected = true + } + }) + if (!selected) { + search(event.target.value) + } + } +}) + +$('#search-icon').on('click', function (event) { + const value = $('.main-search-autocomplete').val() || $('.main-search-autocomplete-mobile').val() + search(value) +}) + +$('.main-search-autocomplete').on('focus', function (_event) { + $('#slash-icon').hide() + $('.search-control').addClass('focused-field') +}) + +$('.main-search-autocomplete').on('focusout', function (_event) { + $('#slash-icon').show() + $('.search-control').removeClass('focused-field') +}) diff --git a/apps/block_scout_web/assets/js/pages/pending_transactions.js b/apps/block_scout_web/assets/js/pages/pending_transactions.js new file mode 100644 index 0000000..beec314 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/pending_transactions.js @@ -0,0 +1,135 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import numeral from 'numeral' +import socket from '../socket' +import { connectElements } from '../lib/redux_helpers.js' +import { batchChannel } from '../lib/utils' +import { createAsyncLoadStore } from '../lib/async_listing_load' +import '../app' + +const BATCH_THRESHOLD = 10 + +export const initialState = { + channelDisconnected: false, + + pendingTransactionCount: null, + + pendingTransactionsBatch: [] +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + return Object.assign({}, state, { + channelDisconnected: true + }) + } + case 'RECEIVED_NEW_TRANSACTION': { + if (state.channelDisconnected) return state + return Object.assign({}, state, { + items: state.items.map((item) => item.includes(action.msg.transactionHash) ? action.msg.transactionHtml : item), + pendingTransactionsBatch: state.pendingTransactionsBatch.filter(transactionHtml => !transactionHtml.includes(action.msg.transactionHash)), + pendingTransactionCount: state.pendingTransactionCount - 1 + }) + } + case 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH': { + if (state.channelDisconnected) return state + + const pendingTransactionCount = state.pendingTransactionCount + action.msgs.length + const pendingTransactionHtml = action.msgs.map(message => message.transactionHtml) + + if (!state.pendingTransactionsBatch.length && action.msgs.length < BATCH_THRESHOLD) { + return Object.assign({}, state, { + items: [ + ...pendingTransactionHtml.reverse(), + ...state.items + ], + pendingTransactionCount + }) + } else { + return Object.assign({}, state, { + pendingTransactionsBatch: [ + ...pendingTransactionHtml.reverse(), + ...state.pendingTransactionsBatch + ], + pendingTransactionCount + }) + } + } + case 'REMOVE_PENDING_TRANSACTION': { + return Object.assign({}, state, { + items: state.items.filter(transactionHtml => !transactionHtml.includes(action.msg.transactionHash)) + }) + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + }, + '[data-selector="channel-batching-count"]': { + render ($el, state, oldState) { + const $channelBatching = $('[data-selector="channel-batching-message"]') + if (state.pendingTransactionsBatch.length) { + $channelBatching.show() + $el[0].innerHTML = numeral(state.pendingTransactionsBatch.length).format() + } else { + $channelBatching.hide() + } + } + }, + '[data-selector="transaction-pending-count"]': { + load ($el) { + return { pendingTransactionCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (oldState.transactionCount === state.transactionCount) return + $el.empty().append(numeral(state.transactionCount).format()) + } + } +} + +const $transactionPendingListPage = $('[data-page="transaction-pending-list"]') +if ($transactionPendingListPage.length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + connectElements({ store, elements }) + + const transactionsChannel = socket.channel('transactions:new_transaction') + transactionsChannel.join() + transactionsChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + transactionsChannel.on('transaction', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_TRANSACTION', + msg: humps.camelizeKeys(msg) + }) + setTimeout(() => store.dispatch({ + type: 'REMOVE_PENDING_TRANSACTION', + msg: humps.camelizeKeys(msg) + }), 1000) + }) + + const pendingTransactionsChannel = socket.channel('transactions:new_pending_transaction') + pendingTransactionsChannel.join() + pendingTransactionsChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + pendingTransactionsChannel.on('pending_transaction', batchChannel((msgs) => store.dispatch({ + type: 'RECEIVED_NEW_PENDING_TRANSACTION_BATCH', + msgs: humps.camelizeKeys(msgs) + }))) +} diff --git a/apps/block_scout_web/assets/js/pages/search-results/search.js b/apps/block_scout_web/assets/js/pages/search-results/search.js new file mode 100644 index 0000000..6f7ff44 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/search-results/search.js @@ -0,0 +1,53 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import { createAsyncLoadStore } from '../../lib/async_listing_load' + +const $searchInput = $('.search-input') + +export const initialState = { + isSearch: false +} + +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="search-results"]').length) { + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + + store.dispatch({ + type: 'PAGE_LOAD' + }) + + $searchInput.on('input', (event) => { + const value = $(event.target).val() + + $('.js-search-results-query-display').text(value) + + const loc = window.location.pathname + + if (value.length >= 3 || value === '') { + store.dispatch({ type: 'START_SEARCH' }) + store.dispatch({ type: 'START_REQUEST' }) + $.ajax({ + url: `${loc}?q=${value}&type=JSON`, + 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' })) + } + }) +} diff --git a/apps/block_scout_web/assets/js/pages/token/overview.js b/apps/block_scout_web/assets/js/pages/token/overview.js new file mode 100644 index 0000000..9f7fa4f --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/token/overview.js @@ -0,0 +1,11 @@ +import $ from 'jquery' +import { appendTokenIcon } from '../../lib/token_icon' + +if ($('[data-page="token-details"]').length) { + const $tokenIconContainer = $('#token-icon') + const chainID = $tokenIconContainer.data('chain-id') + const addressHash = $tokenIconContainer.data('address-hash') + const displayTokenIcons = $tokenIconContainer.data('display-token-icons') + + appendTokenIcon($tokenIconContainer, chainID, addressHash, displayTokenIcons) +} diff --git a/apps/block_scout_web/assets/js/pages/token/search.js b/apps/block_scout_web/assets/js/pages/token/search.js new file mode 100644 index 0000000..a3949f2 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/token/search.js @@ -0,0 +1,58 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import { createAsyncLoadStore } from '../../lib/async_listing_load' +import '../address' + +const $searchInput = $('.tokens-list-search-input') + +export const initialState = { + isSearch: false +} + +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="tokens"]').length) { + let timer + const waitTime = 500 + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + + store.dispatch({ + type: 'PAGE_LOAD' + }) + + $searchInput.on('input', (event) => { + 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/token/token_transfers.js b/apps/block_scout_web/assets/js/pages/token/token_transfers.js new file mode 100644 index 0000000..bbdcf7e --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/token/token_transfers.js @@ -0,0 +1,87 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import URI from 'urijs' +import humps from 'humps' +import { subscribeChannel } from '../../socket' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../../lib/async_listing_load' +import '../token_counters' + +export const initialState = { + addressHash: null, + channelDisconnected: false +} + +export function reducer (state, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + if (state.beyondPageOne) return state + + return Object.assign({}, state, { channelDisconnected: true }) + } + case 'RECEIVED_NEW_TOKEN_TRANSFER': { + if (state.channelDisconnected) return state + + if (state.beyondPageOne) { + return state + } + + return Object.assign({}, state, { items: [action.msg.tokenTransferHtml, ...state.items] }) + } + case 'RECEIVED_NEW_REWARD': { + if (state.channelDisconnected) return state + + return Object.assign({}, state, { items: [action.msg.rewardHtml, ...state.items] }) + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + } +} + +if ($('[data-page="token-transfer-list"]')) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + const addressHash = $('[data-page="token-details"]')[0].dataset.pageAddressHash + const { blockNumber } = humps.camelizeKeys(URI(window.location).query(true)) + + connectElements({ store, elements }) + + store.dispatch({ + type: 'PAGE_LOAD', + addressHash, + beyondPageOne: !!blockNumber + }) + + const tokensChannel = subscribeChannel(`tokens:${addressHash}`) + tokensChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + tokensChannel.on('token_transfer', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_TOKEN_TRANSFER', + msg: humps.camelizeKeys(msg) + }) + }) + + const rewardsChannel = subscribeChannel(`rewards:${addressHash}`) + rewardsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + rewardsChannel.on('new_reward', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_REWARD', + msg: humps.camelizeKeys(msg) + }) + }) +} diff --git a/apps/block_scout_web/assets/js/pages/token_contract.js b/apps/block_scout_web/assets/js/pages/token_contract.js new file mode 100644 index 0000000..e539dec --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/token_contract.js @@ -0,0 +1,2 @@ +import '../lib/smart_contract/index' +import './token_counters' diff --git a/apps/block_scout_web/assets/js/pages/token_counters.js b/apps/block_scout_web/assets/js/pages/token_counters.js new file mode 100644 index 0000000..62b51e0 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/token_counters.js @@ -0,0 +1,97 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import { createStore, connectElements } from '../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../lib/async_listing_load' +import '../app' +import { + openQrModal +} from '../lib/modals' + +export const initialState = { + channelDisconnected: false, + transferCount: null, + tokenHolderCount: null +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'COUNTERS_FETCHED': { + return Object.assign({}, state, { + transferCount: action.transferCount, + tokenHolderCount: action.tokenHolderCount + }) + } + default: + return state + } +} + +const elements = { + '[data-page="counters"]': { + render ($el, state) { + if (state.counters) { + return $el + } + return $el + } + }, + '[token-transfer-count]': { + render ($el, state) { + if (state.transferCount) { + $el.empty().text(state.transferCount + ' Transfers') + return $el.show() + } + } + }, + '[token-holder-count]': { + render ($el, state) { + if (state.tokenHolderCount) { + $el.empty().text(state.tokenHolderCount + ' Addresses') + return $el.show() + } + } + } +} + +function loadCounters (store) { + const $element = $('[data-async-counters]') + const path = $element.data() && $element.data().asyncCounters + function fetchCounters () { + store.dispatch({ type: 'START_REQUEST' }) + $.getJSON(path) + .done(response => store.dispatch(Object.assign({ type: 'COUNTERS_FETCHED' }, humps.camelizeKeys(response)))) + .fail(() => store.dispatch({ type: 'REQUEST_ERROR' })) + .always(() => store.dispatch({ type: 'FINISH_REQUEST' })) + } + + fetchCounters() +} + +const $tokenPage = $('[token-page]') + +if ($tokenPage.length) { + updateCounters() +} + +function updateCounters () { + const store = createStore(reducer) + connectElements({ store, elements }) + loadCounters(store) +} + +if ($('[data-page="token-holders-list"]').length) { + window.onbeforeunload = () => { + window.loading = true + } + + createAsyncLoadStore(reducer, initialState, null) +} + +$('.btn-qr-icon').click(_event => { + openQrModal() +}) diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js new file mode 100644 index 0000000..373ef05 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -0,0 +1,167 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import numeral from 'numeral' +import socket from '../socket' +import { createStore, connectElements } from '../lib/redux_helpers.js' +import '../lib/transaction_input_dropdown' +import '../lib/async_listing_load' +import '../app' +import Swal from 'sweetalert2' +import { compareChainIDs, formatError } from '../lib/smart_contract/common_helpers' + +export const initialState = { + blockNumber: null, + confirmations: null +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'RECEIVED_NEW_BLOCK': { + if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) { + return Object.assign({}, state, { + confirmations: action.msg.blockNumber - state.blockNumber + }) + } else return state + } + default: + return state + } +} + +const elements = { + '[data-selector="block-number"]': { + load ($el) { + return { blockNumber: parseInt($el.text(), 10) } + } + }, + '[data-selector="block-confirmations"]': { + render ($el, state, oldState) { + if (oldState.confirmations !== state.confirmations) { + $el.empty().append(numeral(state.confirmations).format()) + } + } + } +} + +const $transactionDetailsPage = $('[data-page="transaction-details"]') +if ($transactionDetailsPage.length) { + const store = createStore(reducer) + connectElements({ store, elements }) + + const pathParts = window.location.pathname.split('/') + const shouldScroll = pathParts.includes('internal-transactions') || + pathParts.includes('token-transfers') || + pathParts.includes('logs') || + pathParts.includes('token-transfers') || + pathParts.includes('raw-trace') || + pathParts.includes('state') + if (shouldScroll) { + document.getElementById('transaction-tabs').scrollIntoView() + } + + const blocksChannel = socket.channel('blocks:new_block', {}) + blocksChannel.join() + blocksChannel.on('new_block', (msg) => store.dispatch({ + type: 'RECEIVED_NEW_BLOCK', + msg: humps.camelizeKeys(msg) + })) + + const transactionHash = $transactionDetailsPage[0].dataset.pageTransactionHash + const transactionChannel = socket.channel(`transactions:${transactionHash}`, {}) + transactionChannel.join() + transactionChannel.on('collated', () => window.location.reload()) + + $('.js-cancel-transaction').on('click', (event) => { + const btn = $(event.target) + if (!window.ethereum) { + btn + .attr('data-original-title', `Please unlock ${btn.data('from')} account in Metamask`) + .tooltip('show') + + setTimeout(() => { + btn + .attr('data-original-title', null) + .tooltip('dispose') + }, 3000) + return + } + const { chainId: walletChainIdHex } = window.ethereum + compareChainIDs(btn.data('chainId'), walletChainIdHex) + .then(() => { + const txParams = { + from: btn.data('from'), + to: btn.data('from'), + value: 0, + nonce: btn.data('nonce').toString() + } + window.ethereum.request({ + method: 'eth_sendTransaction', + params: [txParams] + }) + .then(function (txHash) { + const successMsg = `Canceling transaction successfully sent to the network. The current one will change the status once canceling transaction will be confirmed.` + Swal.fire({ + title: 'Success', + html: successMsg, + icon: 'success' + }) + .then(() => { + window.location.reload() + }) + }) + .catch(_error => { + btn + .attr('data-original-title', `Please unlock ${btn.data('from')} account in Metamask`) + .tooltip('show') + + setTimeout(() => { + btn + .attr('data-original-title', null) + .tooltip('dispose') + }, 3000) + }) + }) + .catch((error) => { + Swal.fire({ + title: 'Warning', + html: formatError(error), + icon: 'warning' + }) + }) + }) +} + +$(function () { + const $collapseButton = $('[button-collapse-input]') + const $expandButton = $('[button-expand-input]') + + $collapseButton.on('click', event => { + const $button = event.target + const $parent = $button.parentElement + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $collapseButton.classList.add('d-none') + $expandButton.classList.remove('d-none') + $hiddenText.classList.add('d-none') + $placeHolder.classList.remove('d-none') + }) + + $expandButton.on('click', event => { + const $button = event.target + const $parent = $button.parentElement + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $expandButton.classList.add('d-none') + $collapseButton.classList.remove('d-none') + $hiddenText.classList.remove('d-none') + $placeHolder.classList.add('d-none') + }) +}) diff --git a/apps/block_scout_web/assets/js/pages/transactions.js b/apps/block_scout_web/assets/js/pages/transactions.js new file mode 100644 index 0000000..dc306eb --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/transactions.js @@ -0,0 +1,106 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import numeral from 'numeral' +import socket from '../socket' +import { connectElements } from '../lib/redux_helpers' +import { createAsyncLoadStore } from '../lib/random_access_pagination' +import { batchChannel } from '../lib/utils' +import '../app' + +const BATCH_THRESHOLD = 10 + +export const initialState = { + channelDisconnected: false, + transactionCount: null, + transactionsBatch: [] +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + return Object.assign({}, state, { + channelDisconnected: true, + transactionsBatch: [] + }) + } + case 'RECEIVED_NEW_TRANSACTION_BATCH': { + if (state.channelDisconnected || state.beyondPageOne) return state + + const transactionCount = state.transactionCount + action.msgs.length + + if (!state.transactionsBatch.length && action.msgs.length < BATCH_THRESHOLD) { + return Object.assign({}, state, { + items: [ + ...action.msgs.map(msg => msg.transactionHtml).reverse(), + ...state.items + ], + transactionCount + }) + } else { + return Object.assign({}, state, { + transactionsBatch: [ + ...action.msgs.reverse(), + ...state.transactionsBatch + ], + transactionCount + }) + } + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + }, + '[data-selector="channel-batching-count"]': { + render ($el, state, _oldState) { + const $channelBatching = $('[data-selector="channel-batching-message"]') + if (!state.transactionsBatch.length) return $channelBatching.hide() + $channelBatching.show() + $el[0].innerHTML = numeral(state.transactionsBatch.length).format() + } + }, + '[data-selector="transaction-count"]': { + load ($el) { + return { transactionCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (oldState.transactionCount === state.transactionCount) return + $el.empty().append(numeral(state.transactionCount).format()) + } + } +} + +const $transactionListPage = $('[data-page="transaction-list"]') +if ($transactionListPage.length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') + + connectElements({ store, elements }) + + const transactionsChannel = socket.channel('transactions:new_transaction') + transactionsChannel.join() + transactionsChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + transactionsChannel.on('transaction', batchChannel((msgs) => { + if (!store.getState().beyondPageOne && !store.getState().loading) { + store.dispatch({ + type: 'RECEIVED_NEW_TRANSACTION_BATCH', + msgs: humps.camelizeKeys(msgs) + }) + } + })) +} diff --git a/apps/block_scout_web/assets/js/pages/verification_form.js b/apps/block_scout_web/assets/js/pages/verification_form.js new file mode 100644 index 0000000..687eced --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/verification_form.js @@ -0,0 +1,367 @@ +import $ from 'jquery' +import omit from 'lodash.omit' +import humps from 'humps' +import { subscribeChannel } from '../socket' +import { createStore, connectElements } from '../lib/redux_helpers.js' +import '../app' +import Dropzone from 'dropzone' + +export const initialState = { + channelDisconnected: false, + addressHash: null, + newForm: null +} + +export function reducer (state = initialState, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, omit(action, 'type')) + } + case 'CHANNEL_DISCONNECTED': { + return Object.assign({}, state, { + channelDisconnected: true + }) + } + case 'RECEIVED_VERIFICATION_RESULT': { + if (action.msg.verificationResult === 'ok') { + return window.location.replace(window.location.href.split('/contract_verifications')[0].split('/verify')[0] + '/contracts') + } else { + return Object.assign({}, state, { + newForm: action.msg.verificationResult + }) + } + } + default: + return state + } +} + +const elements = { + '[data-selector="channel-disconnected-message"]': { + render ($el, state) { + if (state.channelDisconnected && !window.loading) $el.show() + } + }, + '[data-page="contract-verification"]': { + render ($el, state) { + if (state.newForm) { + $el.replaceWith(state.newForm) + + if ($('.nightly-builds-true').prop('checked')) { filterNightlyBuilds(false, false) } + if ($('.nightly-builds-false').prop('checked')) { filterNightlyBuilds(true, false) } + + initializeDropzone() + state.newForm = null + + return $el + } + return $el + } + } +} + +const $contractVerificationPage = $('[data-page="contract-verification"]') +const $contractVerificationChooseTypePage = $('[data-page="contract-verification-choose-type"]') + +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 { + if (txtValue.toLowerCase().indexOf('nightly') > -1) { + option.style.display = '' + } + } + } +} + +let dropzone + +if ($contractVerificationPage.length) { + window.onbeforeunload = () => { + window.loading = true + } + + const store = createStore(reducer) + const addressHash = $('#smart_contract_address_hash').val() + + store.dispatch({ + type: 'PAGE_LOAD', + addressHash + }) + connectElements({ store, elements }) + + const addressChannel = subscribeChannel(`addresses:${addressHash}`) + + addressChannel.onError(() => store.dispatch({ + type: 'CHANNEL_DISCONNECTED' + })) + addressChannel.on('verification', (msg) => store.dispatch({ + type: 'RECEIVED_VERIFICATION_RESULT', + msg: humps.camelizeKeys(msg) + })) + + $('body').on('click', 'button[data-button-loading="animation"]', function () { + $('#loading').removeClass('d-none') + }) + + $(function () { + initializeDropzone() + + setTimeout(function () { + $('.nightly-builds-false').trigger('click') + }, 10) + + $('body').on('click', '.js-btn-add-contract-libraries', function () { + $('.js-smart-contract-libraries-wrapper').show() + $(this).hide() + }) + + $('body').on('click', '.autodetectfalse', function () { + if ($(this).prop('checked')) { $('.constructor-arguments').show() } + }) + + $('body').on('click', '.autodetecttrue', function () { + if ($(this).prop('checked')) { $('.constructor-arguments').hide() } + }) + + $('body').on('click', '.nightly-builds-true', function () { + if ($(this).prop('checked')) { filterNightlyBuilds(false, true) } + }) + + $('body').on('click', '.nightly-builds-false', function () { + if ($(this).prop('checked')) { filterNightlyBuilds(true, true) } + }) + + $('body').on('click', '.optimization-false', function () { + if ($(this).prop('checked')) { $('.optimization-runs').hide() } + }) + + $('body').on('click', '.optimization-true', function () { + if ($(this).prop('checked')) { $('.optimization-runs').show() } + }) + + $('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() + $('.js-btn-add-contract-libraries').show() + $('.js-add-contract-library-wrapper').show() + }) + + $('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) { + nextContractLibrary.addClass('active') + } + + if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) { + $('.js-add-contract-library-wrapper').hide() + } + }) + + $('body').on('click', '#verify-via-standard-json-input-submit', (event) => { + event.preventDefault() + if (dropzone.files.length > 0) { + dropzone.processQueue() + } else { + $('#loading').addClass('d-none') + } + }) + + $('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() + } else { + $('#loading').addClass('d-none') + } + }) + }) +} else if ($contractVerificationChooseTypePage.length) { + $('.verify-via-flattened-code').on('click', function () { + if ($(this).prop('checked')) { + $('#verify_via_flattened_code_button').show() + $('#verify_via_sourcify_button').hide() + $('#verify_vyper_contract_button').hide() + $('#verify_via_standard_json_input_button').hide() + $('#verify_via_multi_part_files_button').hide() + } + }) + + $('.verify-via-sourcify').on('click', function () { + if ($(this).prop('checked')) { + $('#verify_via_flattened_code_button').hide() + $('#verify_via_sourcify_button').show() + $('#verify_vyper_contract_button').hide() + $('#verify_via_standard_json_input_button').hide() + $('#verify_via_multi_part_files_button').hide() + } + }) + + $('.verify-vyper-contract').on('click', function () { + if ($(this).prop('checked')) { + $('#verify_via_flattened_code_button').hide() + $('#verify_via_sourcify_button').hide() + $('#verify_vyper_contract_button').show() + $('#verify_via_standard_json_input_button').hide() + $('#verify_via_multi_part_files_button').hide() + } + }) + + $('.verify-via-standard-json-input').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').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 0000000..f172741 --- /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 new file mode 100644 index 0000000..1dc41b5 --- /dev/null +++ b/apps/block_scout_web/assets/js/socket.js @@ -0,0 +1,38 @@ +import { Socket } from 'phoenix' +import { locale } from './locale' + +let websocketRootUrl = process.env.SOCKET_ROOT +if (!websocketRootUrl) { + websocketRootUrl = '' +} +if (websocketRootUrl.endsWith('/')) { + websocketRootUrl = websocketRootUrl.slice(0, -1) +} + +const socket = new Socket(websocketRootUrl + '/socket', { params: { locale } }) +socket.connect() + +export default socket + +/** + * Subscribes the client in the channel given the topic. + * + * This function will check if already exist a channel before creating one. This is useful because + * when the client is attempting to create a duplicated subscription, the server will close the + * existing subscription and create a new one. + * + * See more about it in https://hexdocs.pm/phoenix/js/#phoenix. + * + * Returns a Channel instance. + */ +export function subscribeChannel (topic) { + const channel = socket.channels.find(channel => channel.topic === topic) + + if (channel) { + return channel + } else { + const channel = socket.channel(topic, {}) + channel.join() + return channel + } +} 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 new file mode 100644 index 0000000..2a0068a --- /dev/null +++ b/apps/block_scout_web/assets/js/view_specific/address_contract/code_highlighting.js @@ -0,0 +1,30 @@ +import '../../lib/ace/src-min/ace' +import '../../lib/ace/src-min/mode-csharp' +import '../../lib/ace/src-min/theme-chrome' +import $ from 'jquery' + +/* eslint-disable-next-line */ +const Mode = ace.require('ace/mode/csharp').Mode + +const codeMain = $('#code_viewer_main') +const code = codeMain.text() +/* eslint-disable-next-line */ +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, 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/js/view_specific/raw_trace/code_highlighting.js b/apps/block_scout_web/assets/js/view_specific/raw_trace/code_highlighting.js new file mode 100644 index 0000000..5256930 --- /dev/null +++ b/apps/block_scout_web/assets/js/view_specific/raw_trace/code_highlighting.js @@ -0,0 +1,6 @@ +import hljs from 'highlight.js/lib/core' + +// only activate highlighting on pages with this selector +if (document.querySelectorAll('[data-activate-highlight]').length > 0) { + hljs.highlightAll() +} diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json new file mode 100644 index 0000000..d3d1463 --- /dev/null +++ b/apps/block_scout_web/assets/package-lock.json @@ -0,0 +1,32539 @@ +{ + "name": "blockscout", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "blockscout", + "license": "GPL-3.0", + "dependencies": { + "@fortawesome/fontawesome-free": "^6.2.0", + "@tarekraafat/autocomplete.js": "^10.2.7", + "@walletconnect/web3-provider": "^1.8.0", + "assert": "^2.0.0", + "bignumber.js": "^9.1.0", + "bootstrap": "^4.6.0", + "chart.js": "^3.9.1", + "chartjs-adapter-luxon": "^1.2.0", + "clipboard": "^2.0.11", + "core-js": "^3.26.0", + "crypto-browserify": "^3.12.0", + "dropzone": "^5.9.3", + "eth-net-props": "^1.0.41", + "highlight.js": "^11.6.0", + "https-browserify": "^1.0.0", + "humps": "^2.0.1", + "jquery": "^3.6.1", + "js-cookie": "^3.0.1", + "lodash.debounce": "^4.0.8", + "lodash.differenceby": "^4.8.0", + "lodash.find": "^4.6.0", + "lodash.first": "^3.0.0", + "lodash.forin": "^4.4.0", + "lodash.get": "^4.4.2", + "lodash.intersectionby": "^4.7.0", + "lodash.isobject": "^3.0.2", + "lodash.keys": "^4.2.0", + "lodash.last": "^3.0.0", + "lodash.map": "^4.6.0", + "lodash.max": "^4.0.1", + "lodash.merge": "^4.6.2", + "lodash.min": "^4.0.1", + "lodash.noop": "^3.0.1", + "lodash.omit": "^4.5.0", + "lodash.rangeright": "^4.2.0", + "lodash.reduce": "^4.6.0", + "luxon": "^3.1.0", + "moment": "^2.29.4", + "nanomorph": "^5.4.0", + "numeral": "^2.0.6", + "os-browserify": "^0.3.0", + "path-parser": "^6.1.0", + "phoenix": "file:../../../deps/phoenix", + "phoenix_html": "file:../../../deps/phoenix_html", + "pikaday": "^1.8.2", + "popper.js": "^1.14.7", + "reduce-reducers": "^1.0.4", + "redux": "^4.2.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.1.1", + "sweetalert2": "^11.6.7", + "urijs": "^1.19.11", + "url": "^0.11.0", + "util": "^0.12.5", + "web3": "^1.8.0", + "web3modal": "^1.9.9", + "xss": "^1.0.14" + }, + "devDependencies": { + "@babel/core": "^7.20.2", + "@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": "^4.2.2", + "eslint": "^8.27.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "file-loader": "^6.2.0", + "jest": "^29.3.1", + "jest-environment-jsdom": "^29.3.0", + "mini-css-extract-plugin": "^2.6.1", + "postcss": "^8.4.18", + "postcss-loader": "^7.0.1", + "sass": "^1.56.0", + "sass-loader": "^13.1.0", + "style-loader": "^3.3.1", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0" + }, + "engines": { + "node": "16.x", + "npm": "8.x" + } + }, + "../../../deps/phoenix": { + "version": "1.5.13", + "license": "MIT" + }, + "../../../deps/phoenix_html": { + "version": "3.0.4" + }, + "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.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.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "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.20.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz", + "integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.2", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.1", + "@babel/parser": "^7.20.2", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.3.tgz", + "integrity": "sha512-Wl5ilw2UD1+ZYprHVprxHZJCFeBWlzZYOovE4SDYLZnqCOD11j+0QzNeEWKLLTWM7nixrZEh7vNIyb76MyJg3A==", + "dependencies": { + "@babel/types": "^7.20.2", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "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.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "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.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "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.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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "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.18.6", + "regexpu-core": "^5.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "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.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "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.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.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "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/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "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.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.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.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "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.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.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.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.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.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.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "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.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.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.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.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.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.20.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", + "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "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.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", + "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "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.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "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-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": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "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.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "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.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "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.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "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.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "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.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "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.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "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.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.20.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "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.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "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.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "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.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": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@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", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@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", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "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.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "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.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "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": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "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.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "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.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "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.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "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.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "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.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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "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.19.0", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "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.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "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.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "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": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.7.tgz", + "integrity": "sha512-2FoHiSAWkdq4L06uaDN3rS43i6x28desUVxq+zAFuE6kbWYQeiLPJI5IC7Sg9xKYVcrBKSQkVUfH6aeQYbl9QA==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz", + "integrity": "sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.0", + "core-js-compat": "^3.18.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "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.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "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.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "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.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "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", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@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.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.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": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@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", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.0.tgz", + "integrity": "sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "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.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", + "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.1", + "@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.1", + "@babel/types": "^7.20.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", + "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", + "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@eslint/eslintrc": { + "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.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ethereumjs/common": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz", + "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==", + "dependencies": { + "crc-32": "^1.2.0", + "ethereumjs-util": "^7.1.5" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz", + "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==", + "dependencies": { + "@ethereumjs/common": "^2.6.4", + "ethereumjs-util": "^7.1.5" + } + }, + "node_modules/@ethersproject/abi": { + "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.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.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "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/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.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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@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.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "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/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.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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "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/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "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/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.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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "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/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/signing-key": { + "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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@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/strings": { + "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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "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", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@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.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "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/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.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz", + "integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ==", + "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.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", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "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", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "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": "^29.3.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/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, + "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/@jest/console/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/console/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/console/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/console/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/@jest/core": { + "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": "^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", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "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", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/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, + "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/@jest/core/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/core/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/core/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/core/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/@jest/environment": { + "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": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "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": "^29.3.1", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "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": "^29.3.1", + "@jest/expect": "^29.3.1", + "@jest/types": "^29.3.1", + "jest-mock": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "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": "^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.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-message-util": "^29.3.1", + "jest-util": "^29.3.1", + "jest-worker": "^29.3.1", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/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, + "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/@jest/reporters/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/reporters/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/reporters/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/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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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": "^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": "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": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "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": "^29.3.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "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.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": "^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", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/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, + "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/@jest/transform/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/transform/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/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", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "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": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/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, + "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/@jest/types/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/types/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/types/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/types/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/@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", + "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "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/@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" + }, + "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/@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/@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" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "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": "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": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tarekraafat/autocomplete.js": { + "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", + "url": "https://opencollective.com/autocompletejs" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/TarekRaafat" + }, + { + "type": "patreon", + "url": "https://patreon.com/TarekRaafat" + } + ] + }, + "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, + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/babel__core": { + "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", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "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" + } + }, + "node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@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", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@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", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": 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/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/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", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prettier": { + "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", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "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": "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": "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.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.8.0", + "@walletconnect/window-getters": "1.0.0", + "@walletconnect/window-metadata": "1.0.0", + "detect-browser": "5.2.0" + } + }, + "node_modules/@walletconnect/browser-utils/node_modules/detect-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.0.tgz", + "integrity": "sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA==" + }, + "node_modules/@walletconnect/client": { + "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.8.0", + "@walletconnect/iso-crypto": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" + } + }, + "node_modules/@walletconnect/core": { + "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.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" + } + }, + "node_modules/@walletconnect/crypto": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/crypto/-/crypto-1.0.2.tgz", + "integrity": "sha512-+OlNtwieUqVcOpFTvLBvH+9J9pntEqH5evpINHfVxff1XIgwV55PpbdvkHu6r9Ib4WQDOFiD8OeeXs1vHw7xKQ==", + "dependencies": { + "@walletconnect/encoding": "^1.0.1", + "@walletconnect/environment": "^1.0.0", + "@walletconnect/randombytes": "^1.0.2", + "aes-js": "^3.1.2", + "hash.js": "^1.1.7" + } + }, + "node_modules/@walletconnect/encoding": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/encoding/-/encoding-1.0.1.tgz", + "integrity": "sha512-8opL2rs6N6E3tJfsqwS82aZQDL3gmupWUgmvuZ3CGU7z/InZs3R9jkzH8wmYtpbq0sFK3WkJkQRZFFk4BkrmFA==", + "dependencies": { + "is-typedarray": "1.0.0", + "typedarray-to-buffer": "3.1.5" + } + }, + "node_modules/@walletconnect/environment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.0.tgz", + "integrity": "sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ==" + }, + "node_modules/@walletconnect/http-connection": { + "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.8.0", + "@walletconnect/utils": "^1.8.0", + "eventemitter3": "4.0.7", + "xhr2-cookies": "1.1.0" + } + }, + "node_modules/@walletconnect/http-connection/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/@walletconnect/iso-crypto": { + "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.8.0", + "@walletconnect/utils": "^1.8.0" + } + }, + "node_modules/@walletconnect/jsonrpc-types": { + "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.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.1" + } + }, + "node_modules/@walletconnect/mobile-registry": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@walletconnect/mobile-registry/-/mobile-registry-1.4.0.tgz", + "integrity": "sha512-ZtKRio4uCZ1JUF7LIdecmZt7FOLnX72RPSY7aUVu7mj7CSfxDwUn6gBuK6WGtH+NZCldBqDl5DenI5fFSvkKYw==", + "deprecated": "Deprecated in favor of dynamic registry available from: https://github.com/walletconnect/walletconnect-registry" + }, + "node_modules/@walletconnect/qrcode-modal": { + "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.8.0", + "@walletconnect/mobile-registry": "^1.4.0", + "@walletconnect/types": "^1.8.0", + "copy-to-clipboard": "^3.3.1", + "preact": "10.4.1", + "qrcode": "1.4.4" + } + }, + "node_modules/@walletconnect/randombytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/randombytes/-/randombytes-1.0.2.tgz", + "integrity": "sha512-ivgOtAyqQnN0rLQmOFPemsgYGysd/ooLfaDA/ACQ3cyqlca56t3rZc7pXfqJOIETx/wSyoF5XbwL+BqYodw27A==", + "dependencies": { + "@walletconnect/encoding": "^1.0.1", + "@walletconnect/environment": "^1.0.0", + "randombytes": "^2.1.0" + } + }, + "node_modules/@walletconnect/safe-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.0.tgz", + "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg==" + }, + "node_modules/@walletconnect/socket-transport": { + "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.8.0", + "@walletconnect/utils": "^1.8.0", + "ws": "7.5.3" + } + }, + "node_modules/@walletconnect/socket-transport/node_modules/ws": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", + "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@walletconnect/types": { + "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.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.8.0.tgz", + "integrity": "sha512-zExzp8Mj1YiAIBfKNm5u622oNw44WOESzo6hj+Q3apSMIb0Jph9X3GDIdbZmvVZsNPxWDL7uodKgZcCInZv2vA==", + "dependencies": { + "@walletconnect/browser-utils": "^1.8.0", + "@walletconnect/encoding": "^1.0.1", + "@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" + } + }, + "node_modules/@walletconnect/utils/node_modules/bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "node_modules/@walletconnect/utils/node_modules/query-string": { + "version": "6.13.5", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.5.tgz", + "integrity": "sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q==", + "dependencies": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@walletconnect/web3-provider": { + "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" + } + }, + "node_modules/@walletconnect/window-getters": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.0.tgz", + "integrity": "sha512-xB0SQsLaleIYIkSsl43vm8EwETpBzJ2gnzk7e0wMF3ktqiTGS6TFHxcprMl5R44KKh4tCcHCJwolMCaDSwtAaA==" + }, + "node_modules/@walletconnect/window-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.0.tgz", + "integrity": "sha512-9eFvmJxIKCC3YWOL97SgRkKhlyGXkrHwamfechmqszbypFspaSk+t2jQXAEU7YClHF6Qjw5eYOmy1//zFi9/GA==", + "dependencies": { + "@walletconnect/window-getters": "^1.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abab": { + "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.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, + "node_modules/abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "dependencies": { + "xtend": "~4.0.0" + } + }, + "node_modules/accepts": { + "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.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "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" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "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", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "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", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/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/ajv-formats/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/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "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", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/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/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "dependencies": { + "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", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "node_modules/async-mutex": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", + "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/async-mutex/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/autoprefixer": { + "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.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" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "node_modules/babel-jest": { + "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": "^29.3.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.2.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/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, + "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/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-loader": { + "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.2", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "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.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "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.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "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.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-styled-components": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.3.tgz", + "integrity": "sha512-meGStRGv+VuKA/q0/jXxrPNWEm4LPfYIqxooDTdmh8kFsP/Ph7jJG5rUPwUPX3QHUvggwdbgdGpo88P/rRYsVw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.15.4", + "@babel/helper-module-imports": "^7.15.4", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "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": "^29.2.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", + "dependencies": { + "precond": "0.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", + "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/blakejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz", + "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/bn.js": { + "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.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "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": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/bootstrap": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", + "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" + } + }, + "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==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/browserslist": { + "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.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" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "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": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "node_modules/bufferutil": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", + "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/bytes": { + "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" + }, + "engines": { + "node": ">= 10" + } + }, + "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.6.0" + } + }, + "node_modules/cacheable-request": { + "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": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/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": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "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", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "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", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chart.js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", + "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" + }, + "node_modules/chartjs-adapter-luxon": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.2.0.tgz", + "integrity": "sha512-h1lEns7+8cUN/Dmk24dhrT9hpAimKImQxzHpILqXn2kocdzj9b/fDlBa8v8/OMq5rq0uZEx/NV1WpByH4l2/Rw==", + "peerDependencies": { + "chart.js": "^3.0.0", + "luxon": ">=1.0.0" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "engines": { + "node": "*" + } + }, + "node_modules/checkpoint-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz", + "integrity": "sha1-BOTLUWuRQziTWB5tRgGnjpVS6gY=", + "dependencies": { + "functional-red-black-tree": "^1.0.1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "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", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "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": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", + "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "buffer": "^5.5.0", + "class-is": "^1.1.0", + "multibase": "~0.6.0", + "multicodec": "^1.0.0", + "multihashes": "~0.4.15" + }, + "engines": { + "node": ">=4.0.0", + "npm": ">=3.0.0" + } + }, + "node_modules/cids/node_modules/multicodec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", + "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "buffer": "^5.6.0", + "varint": "^5.0.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/class-is": { + "version": "1.1.0", + "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.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", + "tiny-emitter": "^2.0.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-response": { + "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": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "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.2", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", + "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "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=", + "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", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-hash": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", + "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", + "dependencies": { + "cids": "^0.7.1", + "multicodec": "^0.5.5", + "multihashes": "^0.4.15" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + }, + "node_modules/copy-to-clipboard": { + "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": "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.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", + "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "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.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-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", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/crc-32": { + "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" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/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/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-fetch": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.6.tgz", + "integrity": "sha512-9JZz+vXCmfKUZ68zAptS7k4Nu8e2qcibe7WVZYps7sAgk5R8GYTc+T1WR0v1rlP9HxgARmOX1UTIJZFytajpNA==", + "dependencies": { + "node-fetch": "^2.6.7", + "whatwg-fetch": "^2.0.4" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-declaration-sorter": { + "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, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", + "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.27.0 || ^5.0.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", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-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==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "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.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": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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": { + "@types/node": "*", + "jest-util": "^29.1.2", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/css-select": { + "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": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" + }, + "node_modules/cssnano": { + "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.2.12", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "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" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "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" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/cssom": { + "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": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "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.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "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.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=", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "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": "^3.1.0" + }, + "engines": { + "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": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "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", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "dependencies": { + "abstract-leveldown": "~2.6.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegate": { + "version": "3.2.0", + "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=", + "dev": true, + "optional": true, + "peer": true, + "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", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "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", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz", + "integrity": "sha512-eAcRiEPTs7utXWPaAgu/OX1HRJpxW7xSHpw4LTDrGFaeWnJ37HRlqpUkKsDm0AoTbtrvHQhH+5U2Cd87EGhJTg==" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/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/dijkstrajs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz", + "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "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", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "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": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domhandler": { + "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" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dropzone": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", + "integrity": "sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA==" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "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", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/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/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/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==", + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "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", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "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", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "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", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "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.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": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "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", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "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": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.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": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.27.0.tgz", + "integrity": "sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==", + "dev": true, + "dependencies": { + "@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", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^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", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/eslint-plugin-n": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.2.2.tgz", + "integrity": "sha512-MLjZVAv4TiCIoXqjibNqCJjLkGHfrOY3XZ0ZBLoW0OnS3o98PUBnzB/kfp8dCz/4A4Y18jjX50PRnqI4ACFY1Q==", + "dev": true, + "peer": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.9.0", + "minimatch": "^3.1.2", + "resolve": "^1.10.1", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-plugin-n/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-promise": { + "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" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/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, + "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/eslint/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/eslint/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/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "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", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "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", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "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", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "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.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eth-block-tracker": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.4.3.tgz", + "integrity": "sha512-A8tG4Z4iNg4mw5tP1Vung9N9IjgMNqpiMoJ/FouSFwNCGHv2X0mmOYwtQOJzki6XN7r7Tyo01S29p7b224I4jw==", + "dependencies": { + "@babel/plugin-transform-runtime": "^7.5.5", + "@babel/runtime": "^7.5.5", + "eth-query": "^2.1.0", + "json-rpc-random-id": "^1.0.1", + "pify": "^3.0.0", + "safe-event-emitter": "^1.0.1" + } + }, + "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": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", + "dependencies": { + "idna-uts46-hx": "^2.3.1", + "js-sha3": "^0.5.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": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==" + }, + "node_modules/eth-json-rpc-filters": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz", + "integrity": "sha512-DGtqpLU7bBg63wPMWg1sCpkKCf57dJ+hj/k3zF26anXMzkmtSBDExL8IhUu7LUd34f0Zsce3PYNO2vV2GaTzaw==", + "dependencies": { + "@metamask/safe-event-emitter": "^2.0.0", + "async-mutex": "^0.2.6", + "eth-json-rpc-middleware": "^6.0.0", + "eth-query": "^2.1.2", + "json-rpc-engine": "^6.1.0", + "pify": "^5.0.0" + } + }, + "node_modules/eth-json-rpc-filters/node_modules/pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eth-json-rpc-infura": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-5.1.0.tgz", + "integrity": "sha512-THzLye3PHUSGn1EXMhg6WTLW9uim7LQZKeKaeYsS9+wOBcamRiCQVGHa6D2/4P0oS0vSaxsBnU/J6qvn0MPdow==", + "dependencies": { + "eth-json-rpc-middleware": "^6.0.0", + "eth-rpc-errors": "^3.0.0", + "json-rpc-engine": "^5.3.0", + "node-fetch": "^2.6.0" + } + }, + "node_modules/eth-json-rpc-infura/node_modules/json-rpc-engine": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz", + "integrity": "sha512-rAffKbPoNDjuRnXkecTjnsE3xLLrb00rEkdgalINhaYVYIxDwWtvYBr9UFbhTvPB1B2qUOLoFd/cV6f4Q7mh7g==", + "dependencies": { + "eth-rpc-errors": "^3.0.0", + "safe-event-emitter": "^1.0.1" + } + }, + "node_modules/eth-json-rpc-middleware": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-6.0.0.tgz", + "integrity": "sha512-qqBfLU2Uq1Ou15Wox1s+NX05S9OcAEL4JZ04VZox2NS0U+RtCMjSxzXhLFWekdShUPZ+P8ax3zCO2xcPrp6XJQ==", + "dependencies": { + "btoa": "^1.2.1", + "clone": "^2.1.1", + "eth-query": "^2.1.2", + "eth-rpc-errors": "^3.0.0", + "eth-sig-util": "^1.4.2", + "ethereumjs-util": "^5.1.2", + "json-rpc-engine": "^5.3.0", + "json-stable-stringify": "^1.0.1", + "node-fetch": "^2.6.1", + "pify": "^3.0.0", + "safe-event-emitter": "^1.0.1" + } + }, + "node_modules/eth-json-rpc-middleware/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/eth-json-rpc-middleware/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/eth-json-rpc-middleware/node_modules/json-rpc-engine": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz", + "integrity": "sha512-rAffKbPoNDjuRnXkecTjnsE3xLLrb00rEkdgalINhaYVYIxDwWtvYBr9UFbhTvPB1B2qUOLoFd/cV6f4Q7mh7g==", + "dependencies": { + "eth-rpc-errors": "^3.0.0", + "safe-event-emitter": "^1.0.1" + } + }, + "node_modules/eth-lib": { + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz", + "integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==", + "dependencies": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "nano-json-stream-parser": "^0.1.2", + "servify": "^0.1.12", + "ws": "^3.0.0", + "xhr-request-promise": "^0.1.2" + } + }, + "node_modules/eth-lib/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/eth-lib/node_modules/ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dependencies": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "node_modules/eth-net-props": { + "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" + } + }, + "node_modules/eth-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz", + "integrity": "sha1-1nQdkAAQa1FRDHLbktY2VFam2l4=", + "dependencies": { + "json-rpc-random-id": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "node_modules/eth-rpc-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-3.0.0.tgz", + "integrity": "sha512-iPPNHPrLwUlR9xCSYm7HHQjWBasor3+KZfRvwEWxMz3ca0yqnlBeJrnyphkGIXZ4J7AMAaOLmwy4AWhnxOiLxg==", + "dependencies": { + "fast-safe-stringify": "^2.0.6" + } + }, + "node_modules/eth-sig-util": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", + "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", + "deprecated": "Deprecated in favor of '@metamask/eth-sig-util'", + "dependencies": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "ethereumjs-util": "^5.1.1" + } + }, + "node_modules/eth-sig-util/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/eth-sig-util/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereum-bloom-filters": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", + "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "dependencies": { + "js-sha3": "^0.8.0" + } + }, + "node_modules/ethereum-common": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", + "integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==" + }, + "node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/ethereumjs-abi": { + "version": "0.6.8", + "resolved": "git+ssh://git@github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + } + }, + "node_modules/ethereumjs-abi/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/ethereumjs-abi/node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "node_modules/ethereumjs-account": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-2.0.5.tgz", + "integrity": "sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA==", + "dependencies": { + "ethereumjs-util": "^5.0.0", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereumjs-account/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/ethereumjs-account/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereumjs-block": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz", + "integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==", + "deprecated": "New package name format for new versions: @ethereumjs/block. Please update.", + "dependencies": { + "async": "^2.0.1", + "ethereum-common": "0.2.0", + "ethereumjs-tx": "^1.2.2", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + } + }, + "node_modules/ethereumjs-block/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/ethereumjs-block/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereumjs-common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz", + "integrity": "sha512-hTfZjwGX52GS2jcVO6E2sx4YuFnf0Fhp5ylo4pEPhEffNln7vS59Hr5sLnp3/QCazFLluuBZ+FZ6J5HTp0EqCA==", + "deprecated": "New package name format for new versions: @ethereumjs/common. Please update." + }, + "node_modules/ethereumjs-tx": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", + "integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==", + "deprecated": "New package name format for new versions: @ethereumjs/tx. Please update.", + "dependencies": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "node_modules/ethereumjs-tx/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/ethereumjs-tx/node_modules/ethereum-common": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz", + "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=" + }, + "node_modules/ethereumjs-tx/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereumjs-util": { + "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", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ethereumjs-util/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/ethereumjs-vm": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz", + "integrity": "sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw==", + "deprecated": "New package name format for new versions: @ethereumjs/vm. Please update.", + "dependencies": { + "async": "^2.1.2", + "async-eventemitter": "^0.2.2", + "ethereumjs-account": "^2.0.3", + "ethereumjs-block": "~2.2.0", + "ethereumjs-common": "^1.1.0", + "ethereumjs-util": "^6.0.0", + "fake-merkle-patricia-tree": "^1.0.1", + "functional-red-black-tree": "^1.0.1", + "merkle-patricia-tree": "^2.3.2", + "rustbn.js": "~0.2.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereumjs-vm/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/ethereumjs-vm/node_modules/ethereumjs-block": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz", + "integrity": "sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==", + "deprecated": "New package name format for new versions: @ethereumjs/block. Please update.", + "dependencies": { + "async": "^2.0.1", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.1", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + } + }, + "node_modules/ethereumjs-vm/node_modules/ethereumjs-block/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereumjs-vm/node_modules/ethereumjs-tx": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", + "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", + "deprecated": "New package name format for new versions: @ethereumjs/tx. Please update.", + "dependencies": { + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.0.0" + } + }, + "node_modules/ethereumjs-vm/node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "node_modules/ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", + "dependencies": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "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": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" + }, + "node_modules/ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dependencies": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "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/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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "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.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "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": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "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", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fake-merkle-patricia-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz", + "integrity": "sha1-S4w6z7Ugr635hgsfFM2M40As3dM=", + "dependencies": { + "checkpoint-store": "^1.1.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "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" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.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", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "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.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir/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/find-cache-dir/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "node_modules/foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "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", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "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", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "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", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "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": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": 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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "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", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "dependencies": { + "delegate": "^3.1.2" + } + }, + "node_modules/got": { + "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" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.9", + "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", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "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", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/highlight.js": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", + "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "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": "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": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http-errors": { + "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": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "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": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==" + }, + "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, + "optional": true, + "peer": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "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.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", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "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", + "integrity": "sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idna-uts46-hx": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", + "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", + "dependencies": { + "punycode": "2.1.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/idna-uts46-hx/node_modules/punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, + "node_modules/immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", + "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/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/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "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", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "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/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "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", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fn/-/is-fn-1.0.0.tgz", + "integrity": "sha1-lUPV3nvPWwiiLsiiC65uKG1RDYw=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=", + "engines": { + "node": ">=6.5.0", + "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", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": ">=8" + } + }, + "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=", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", + "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-weakref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", + "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "dependencies": { + "call-bind": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "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", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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/istanbul-lib-report/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/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "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", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "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": "^29.3.1", + "@jest/types": "^29.3.1", + "import-local": "^3.0.2", + "jest-cli": "^29.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "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": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "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": "^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", + "is-generator-fn": "^2.0.0", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/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, + "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/jest-circus/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-circus/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-circus/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-circus/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/jest-cli": { + "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": "^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.9", + "import-local": "^3.0.2", + "jest-config": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/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, + "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/jest-cli/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-cli/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-cli/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-cli/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/jest-config": { + "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.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.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", + "parse-json": "^5.2.0", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "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 + } + } + }, + "node_modules/jest-config/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/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, + "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/jest-config/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-config/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-config/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-config/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/jest-diff": { + "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": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/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, + "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/jest-diff/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-diff/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-diff/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-diff/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/jest-docblock": { + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "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": "^29.3.1", + "chalk": "^4.0.0", + "jest-get-type": "^29.2.0", + "jest-util": "^29.3.1", + "pretty-format": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/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, + "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/jest-each/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-each/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-each/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-each/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/jest-environment-jsdom": { + "version": "29.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.0.tgz", + "integrity": "sha512-xFLbMR4OF4lntNcO9LthJdPRbI9WgfFlG73aQS6wQ54+v4oSAp8T4FKUw0add+Z+Ghu/dirRxuvc4FzzN5kRxw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.3.0", + "@jest/fake-timers": "^29.3.0", + "@jest/types": "^29.2.1", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.3.0", + "jest-util": "^29.2.1", + "jsdom": "^20.0.0" + }, + "engines": { + "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": "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": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "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": "^29.3.1", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "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.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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": { + "@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-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": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-leak-detector": { + "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": "^29.2.0", + "pretty-format": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "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": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/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, + "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/jest-matcher-utils/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-matcher-utils/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-matcher-utils/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-matcher-utils/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/jest-message-util": { + "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": "^29.3.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/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, + "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/jest-message-util/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-message-util/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-message-util/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-message-util/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/jest-mock": { + "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": "^29.3.1", + "@types/node": "*", + "jest-util": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "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": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "jest-pnp-resolver": "^1.2.2", + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "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-regex-util": "^29.2.0", + "jest-snapshot": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/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, + "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/jest-resolve/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-resolve/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-resolve/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-resolve/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/jest-runner": { + "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": "^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.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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/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, + "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/jest-runner/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-runner/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-runner/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-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", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "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", + "glob": "^7.1.3", + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/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, + "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/jest-runtime/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-runtime/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-runtime/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-runtime/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/jest-snapshot": { + "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.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.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": "^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": "^29.3.1", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/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, + "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/jest-snapshot/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-snapshot/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-snapshot/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-snapshot/node_modules/semver": { + "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" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/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/jest-util": { + "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": "^29.3.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/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, + "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/jest-util/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-util/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-util/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-util/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/jest-validate": { + "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": "^29.3.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.2.0", + "leven": "^3.1.0", + "pretty-format": "^29.3.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/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, + "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/jest-validate/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-validate/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-validate/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-validate/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/jest-watcher": { + "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": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.3.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/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, + "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/jest-watcher/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-watcher/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-watcher/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-watcher/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/jest-worker": { + "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": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/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-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/jquery": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + }, + "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", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/jsdom": { + "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.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.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": "^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": "^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": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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, + "engines": { + "node": ">= 10" + } + }, + "node_modules/jsdom/node_modules/form-data": { + "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", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "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", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "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", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-rpc-engine": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", + "integrity": "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==", + "dependencies": { + "@metamask/safe-event-emitter": "^2.0.0", + "eth-rpc-errors": "^4.0.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/json-rpc-engine/node_modules/eth-rpc-errors": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", + "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", + "dependencies": { + "fast-safe-stringify": "^2.0.6" + } + }, + "node_modules/json-rpc-random-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", + "integrity": "sha1-uknZat7RRE27jaPSA3SKy7zeyMg=" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/keccak": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", + "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keyv": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", + "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/keyvaluestorage-interface": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", + "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==" + }, + "node_modules/level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "dependencies": { + "errno": "~0.1.1" + } + }, + "node_modules/level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "dependencies": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + } + }, + "node_modules/level-iterator-stream/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/level-iterator-stream/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/level-iterator-stream/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "dependencies": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + } + }, + "node_modules/level-ws/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/level-ws/node_modules/object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "node_modules/level-ws/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/level-ws/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/level-ws/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "dependencies": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "node_modules/levelup/node_modules/semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "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" + } + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", + "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "node_modules/lodash.differenceby": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash.differenceby/-/lodash.differenceby-4.8.0.tgz", + "integrity": "sha1-z9WelDU69d5R2l0wLKTr/zP6rFc=" + }, + "node_modules/lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=" + }, + "node_modules/lodash.first": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.first/-/lodash.first-3.0.0.tgz", + "integrity": "sha1-Xa4YDX+BjuZfxbIQsQSnu++YoWo=" + }, + "node_modules/lodash.forin": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.forin/-/lodash.forin-4.4.0.tgz", + "integrity": "sha1-XT8grlZAEfvog4H32YlJyclRlzE=" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "node_modules/lodash.intersectionby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.intersectionby/-/lodash.intersectionby-4.7.0.tgz", + "integrity": "sha1-EvEl5NoAsiKQ/r2htsG2i7klUSU=" + }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + }, + "node_modules/lodash.keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz", + "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=" + }, + "node_modules/lodash.last": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", + "integrity": "sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw=" + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + }, + "node_modules/lodash.max": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.max/-/lodash.max-4.0.1.tgz", + "integrity": "sha1-hzVWbGGLNan3YFILSHrnllivE2o=" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.min": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.min/-/lodash.min-4.0.1.tgz", + "integrity": "sha1-SsG5qLr4ttKKaQ1xZRJRDPwUcIw=" + }, + "node_modules/lodash.noop": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", + "integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=" + }, + "node_modules/lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=" + }, + "node_modules/lodash.rangeright": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.rangeright/-/lodash.rangeright-4.2.0.tgz", + "integrity": "sha1-dCrF5C+R9oKiwLaHwpt52TIzkEI=" + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" + }, + "node_modules/luxon": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.0.tgz", + "integrity": "sha512-7w6hmKC0/aoWnEsmPCu5Br54BmbmUp5GfcqBxQngRcXJ+q5fdfjEzn7dxmJh2YdDhgW8PccYtlWKSv4tQkrTQg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "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", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "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", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "dependencies": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + } + }, + "node_modules/memdown/node_modules/abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "dependencies": { + "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/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==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "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": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "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" + } + }, + "node_modules/merkle-patricia-tree/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "node_modules/merkle-patricia-tree/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/merkle-patricia-tree/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/merkle-patricia-tree/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==", + "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/merkle-patricia-tree/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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/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/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "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.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.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dependencies": { + "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.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "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==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.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", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-promise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", + "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": "*" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mock-fs": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", + "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multibase": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", + "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + }, + "node_modules/multicodec": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", + "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "varint": "^5.0.0" + } + }, + "node_modules/multihashes": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", + "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", + "dependencies": { + "buffer": "^5.5.0", + "multibase": "^0.7.0", + "varint": "^5.0.0" + } + }, + "node_modules/multihashes/node_modules/multibase": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", + "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "base-x": "^3.0.8", + "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": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==" + }, + "node_modules/nanoassert": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", + "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" + }, + "node_modules/nanoid": { + "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" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomorph": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/nanomorph/-/nanomorph-5.4.3.tgz", + "integrity": "sha512-uPP5y0x21KISffZCKHh1A0QW0RHZFQS0BR7LetlHBlay6UWAbjwhjiJTxOO6JeMHko5Cigl617zFoGrYFJ8ZLg==", + "dependencies": { + "nanoassert": "^1.1.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "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" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/next-tick": { + "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", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "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", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "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-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==", + "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_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==", + "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": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "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/node-sass": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", + "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", + "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.1", + "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_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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "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.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" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "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": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", + "dependencies": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "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": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" + }, + "node_modules/numeral": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", + "integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=", + "engines": { + "node": "*" + } + }, + "node_modules/nwsapi": { + "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": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oboe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", + "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", + "dependencies": { + "http-https": "^1.0.0" + } + }, + "node_modules/on-finished": { + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "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-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-headers": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.4.tgz", + "integrity": "sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw==" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "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", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "engines": { + "node": ">=4" + } + }, + "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=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-parser": { + "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": "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": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/phoenix": { + "resolved": "../../../deps/phoenix", + "link": true + }, + "node_modules/phoenix_html": { + "resolved": "../../../deps/phoenix_html", + "link": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "engines": { + "node": ">=4" + } + }, + "node_modules/pikaday": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz", + "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew==" + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/postcss": { + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "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.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "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.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "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": { + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "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" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "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" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "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" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "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" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-loader": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-merge-longhand": { + "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": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "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": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "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.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "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": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "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": { + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "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": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "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" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "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": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "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.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "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": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "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.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "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": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "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.6", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "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": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "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.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "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": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "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.6", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "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": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "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", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "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.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "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": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/preact": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.4.1.tgz", + "integrity": "sha512-WKrRpCSwL2t3tpOOGhf2WfTpcmbpxaWtDbdJdKdjd0aEiTkvOmS4NBkG6kzlaAHI9AkQ3iVqbFWM3Ei7mZ4o1Q==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "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/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "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", + "integrity": "sha1-XSp0kBC/tn2WNZj805YHRqaP7vc=", + "dependencies": { + "is-fn": "^1.0.0", + "set-immediate-shim": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/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/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz", + "integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==", + "dependencies": { + "buffer": "^5.4.3", + "buffer-alloc": "^1.2.0", + "buffer-from": "^1.1.1", + "dijkstrajs": "^1.0.1", + "isarray": "^2.0.1", + "pngjs": "^3.3.0", + "yargs": "^13.2.4" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/qrcode/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/qrcode/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "node_modules/qrcode/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/qrcode/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/qrcode/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/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==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qrcode/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "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==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "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", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "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.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "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", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "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==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reduce-reducers": { + "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.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" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "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" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/regenerator-transform": { + "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" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "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": "^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" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "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.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" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "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", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "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", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": ">=8" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "dependencies": { + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-event-emitter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz", + "integrity": "sha512-e1wFe99A91XYYxoQbcq2ZJUWurxEyP8vfz7A7vuUe1s95q8r5ebraVaA1BukYJcpM6V16ugWoD9vngi8Ccu5fg==", + "deprecated": "Renamed to @metamask/safe-event-emitter", + "dependencies": { + "events": "^3.0.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sass": { + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.0.tgz", + "integrity": "sha512-WFJ9XrpkcnqZcYuLRJh5qiV6ibQOR4AezleeEjTjMsCocYW59dEG19U3fwTTXxzi2Ed3yjPBp727hbbj53pHFw==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-graph": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", + "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "glob": "^7.0.0", + "lodash": "^4.17.11", + "scss-tokenizer": "^0.4.3", + "yargs": "^17.2.1" + }, + "bin": { + "sassgraph": "bin/sassgraph" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/sass-loader": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.1.0.tgz", + "integrity": "sha512-tZS1RJQ2n2+QNyf3CCAo1H562WjL/5AM6Gi8YcPVVoNxQX8d19mx8E+8fRrMWsyc93ZL6Q8vZDSM0FHVTJaVnQ==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/saxes": { + "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": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "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/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, + "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/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, + "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/scss-tokenizer": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", + "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "js-base64": "^2.4.9", + "source-map": "^0.7.3" + } + }, + "node_modules/scss-tokenizer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/search-params": { + "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", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, + "node_modules/semaphore": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", + "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "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": "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": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "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": "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", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "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.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/servify": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", + "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", + "dependencies": { + "body-parser": "^1.16.0", + "cors": "^2.8.1", + "express": "^4.14.0", + "request": "^2.79.0", + "xhr": "^2.3.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "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": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz", + "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", + "dependencies": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "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", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "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.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/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.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", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "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": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "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_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "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": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", + "dependencies": { + "is-hex-prefixed": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "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", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/styled-components": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz", + "integrity": "sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^0.8.8", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/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, + "dependencies": { + "browserslist": "^4.16.6", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/swarm-js": { + "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": "^11.8.5", + "mime-types": "^2.1.16", + "mkdirp-promise": "^5.0.1", + "mock-fs": "^4.1.0", + "setimmediate": "^1.0.5", + "tar": "^4.0.2", + "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", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/swarm-js/node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dependencies": { + "minipass": "^2.6.0" + } + }, + "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": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=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": ">=10.19.0" + } + }, + "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": ">=8" + } + }, + "node_modules/swarm-js/node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/swarm-js/node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dependencies": { + "minipass": "^2.9.0" + } + }, + "node_modules/swarm-js/node_modules/mkdirp": { + "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.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/swarm-js/node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, + "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": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/swarm-js/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/swarm-js/node_modules/tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dependencies": { + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + }, + "engines": { + "node": ">=4.5" + } + }, + "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.6.7", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.6.7.tgz", + "integrity": "sha512-nbsmUtfTodJvZ873sn61U0brrHtn8xi8wfn+bVVc9IV05fgOdDMhAhdQ2zQJBYsvggbbp46ZFHMr0/fZJnZPtQ==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "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/terser": { + "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-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", + "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", + "dev": true, + "dependencies": { + "jest-worker": "^27.0.6", + "p-limit": "^3.1.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "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.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">= 4.0.0" + } + }, + "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, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "glob": "^7.1.2" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/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_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "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", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "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": { + "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": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==" + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "node_modules/utf-8-validate": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", + "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, + "node_modules/util": { + "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", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "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.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "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" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "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": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "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", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/web3": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.0.tgz", + "integrity": "sha512-sldr9stK/SALSJTgI/8qpnDuBJNMGjVR84hJ+AcdQ+MLBGLMGsCDNubCoyO6qgk1/Y9SQ7ignegOI/7BPLoiDA==", + "hasInstallScript": true, + "dependencies": { + "web3-bzz": "1.8.0", + "web3-core": "1.8.0", + "web3-eth": "1.8.0", + "web3-eth-personal": "1.8.0", + "web3-net": "1.8.0", + "web3-shh": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-bzz": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.0.tgz", + "integrity": "sha512-caDtdKeLi7+2Vb+y+cq2yyhkNjnxkFzVW0j1DtemarBg3dycG1iEl75CVQMLNO6Wkg+HH9tZtRnUyFIe5LIUeQ==", + "hasInstallScript": true, + "dependencies": { + "@types/node": "^12.12.6", + "got": "12.1.0", + "swarm-js": "^0.1.40" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-bzz/node_modules/@types/node": { + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.0.tgz", + "integrity": "sha512-9sCA+Z02ci6zoY2bAquFiDjujRwmSKHiSGi4B8IstML8okSytnzXk1izHYSynE7ahIkguhjWAuXFvX76F5rAbA==", + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-requestmanager": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-helpers": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.0.tgz", + "integrity": "sha512-nMAVwZB3rEp/khHI2BvFy0e/xCryf501p5NGjswmJtEM+Zrd3Biaw52JrB1qAZZIzCA8cmLKaOgdfamoDOpWdw==", + "dependencies": { + "web3-eth-iban": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-method": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.0.tgz", + "integrity": "sha512-c94RAzo3gpXwf2rf8rL8C77jOzNWF4mXUoUfZYYsiY35cJFd46jQDPI00CB5+ZbICTiA5mlVzMj4e7jAsTqiLA==", + "dependencies": { + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.0", + "web3-core-promievent": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-promievent": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.0.tgz", + "integrity": "sha512-FGLyjAuOaAQ+ZhV6iuw9tg/9WvIkSZXKHQ4mdTyQ8MxVraOtFivOCbuLLsGgapfHYX+RPxsc1j1YzQjKoupagQ==", + "dependencies": { + "eventemitter3": "4.0.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-requestmanager": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.0.tgz", + "integrity": "sha512-2AoYCs3Owl5foWcf4uKPONyqFygSl9T54L8b581U16nsUirjhoTUGK/PBhMDVcLCmW4QQmcY5A8oPFpkQc1TTg==", + "dependencies": { + "util": "^0.12.0", + "web3-core-helpers": "1.8.0", + "web3-providers-http": "1.8.0", + "web3-providers-ipc": "1.8.0", + "web3-providers-ws": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-subscriptions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.0.tgz", + "integrity": "sha512-7lHVRzDdg0+Gcog55lG6Q3D8JV+jN+4Ly6F8cSn9xFUAwOkdbgdWsjknQG7t7CDWy21DQkvdiY2BJF8S68AqOA==", + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.0" + }, + "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.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.8.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.0.tgz", + "integrity": "sha512-hist52os3OT4TQFB/GxPSMxTh3995sz6LPvQpPvj7ktSbpg9RNSFaSsPlCT63wUAHA3PZb1FemkAIeQM5t72Lw==", + "dependencies": { + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-eth-abi": "1.8.0", + "web3-eth-accounts": "1.8.0", + "web3-eth-contract": "1.8.0", + "web3-eth-ens": "1.8.0", + "web3-eth-iban": "1.8.0", + "web3-eth-personal": "1.8.0", + "web3-net": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-abi": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.0.tgz", + "integrity": "sha512-xPeMb2hS9YLQK/Q5YZpkcmzoRGM+/R8bogSrYHhNC3hjZSSU0YRH+1ZKK0f9YF4qDZaPMI8tKWIMSCDIpjG6fg==", + "dependencies": { + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-accounts": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.0.tgz", + "integrity": "sha512-HQ/MDSv4bexwJLvnqsM6xpGE7c2NVOqyhzOZFyMUKXbIwIq85T3TaLnM9pCN7XqMpDcfxqiZ3q43JqQVkzHdmw==", + "dependencies": { + "@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.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-accounts/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-accounts/node_modules/eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dependencies": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "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.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/web3-eth-contract": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.0.tgz", + "integrity": "sha512-6xeXhW2YoCrz2Ayf2Vm4srWiMOB6LawkvxWJDnUWJ8SMATg4Pgu42C/j8rz/enXbYWt2IKuj0kk8+QszxQbK+Q==", + "dependencies": { + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-promievent": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-eth-abi": "1.8.0", + "web3-utils": "1.8.0" + }, + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.0.tgz", + "integrity": "sha512-/eFbQEwvsMOEiOhw9/iuRXCsPkqAmHHWuFOrThQkozRgcnSTRnvxkkRC/b6koiT5/HaKeUs4yQDg+/ixsIxZxA==", + "dependencies": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-promievent": "1.8.0", + "web3-eth-abi": "1.8.0", + "web3-eth-contract": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-iban": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.0.tgz", + "integrity": "sha512-4RbvUxcMpo/e5811sE3a6inJ2H4+FFqUVmlRYs0RaXaxiHweahSRBNcpO0UWgmlePTolj0rXqPT2oEr0DuC8kg==", + "dependencies": { + "bn.js": "^5.2.1", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-personal": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.0.tgz", + "integrity": "sha512-L7FT4nR3HmsfZyIAhFpEctKkYGOjRC2h6iFKs9gnFCHZga8yLcYcGaYOBIoYtaKom99MuGBoosayWt/Twh7F5A==", + "dependencies": { + "@types/node": "^12.12.6", + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-net": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-personal/node_modules/@types/node": { + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.0.tgz", + "integrity": "sha512-kX6EAacK7QrOe7DOh0t5yHS5q2kxZmTCxPVwSz9io9xBeE4n4UhmzGJ/VfhP2eM3OPKYeypcR3LEO6zZ8xn2vw==", + "dependencies": { + "web3-core": "1.8.0", + "web3-core-method": "1.8.0", + "web3-utils": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-provider-engine": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-16.0.1.tgz", + "integrity": "sha512-/Eglt2aocXMBiDj7Se/lyZnNDaHBaoJlaUfbP5HkLJQC/HlGbR+3/W+dINirlJDhh7b54DzgykqY7ksaU5QgTg==", + "dependencies": { + "async": "^2.5.0", + "backoff": "^2.5.0", + "clone": "^2.0.0", + "cross-fetch": "^2.1.0", + "eth-block-tracker": "^4.4.2", + "eth-json-rpc-filters": "^4.2.1", + "eth-json-rpc-infura": "^5.1.0", + "eth-json-rpc-middleware": "^6.0.0", + "eth-rpc-errors": "^3.0.0", + "eth-sig-util": "^1.4.2", + "ethereumjs-block": "^1.2.2", + "ethereumjs-tx": "^1.2.0", + "ethereumjs-util": "^5.1.5", + "ethereumjs-vm": "^2.3.4", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "readable-stream": "^2.2.9", + "request": "^2.85.0", + "semaphore": "^1.0.3", + "ws": "^5.1.1", + "xhr": "^2.2.0", + "xtend": "^4.0.1" + } + }, + "node_modules/web3-provider-engine/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-provider-engine/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/web3-provider-engine/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==", + "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/web3-provider-engine/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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/web3-provider-engine/node_modules/ws": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", + "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/web3-providers-http": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.0.tgz", + "integrity": "sha512-/MqxwRzExohBWW97mqlCSW/+NHydGRyoEDUS1bAIF2YjfKFwyRtHgrEzOojzkC9JvB+8LofMvbXk9CcltpZapw==", + "dependencies": { + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.0" + }, + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.0.tgz", + "integrity": "sha512-tAXHtVXNUOgehaBU8pzAlB3qhjn/PRpjdzEjzHNFqtRRTwzSEKOJxFeEhaUA4FzHnTlbnrs8ujHWUitcp1elfg==", + "dependencies": { + "oboe": "2.1.5", + "web3-core-helpers": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-providers-ws": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.0.tgz", + "integrity": "sha512-bcZtSifsqyJxwkfQYamfdIRp4nhj9eJd7cxHg1uUkfLJK125WP96wyJL1xbPt7qt0MpfnTFn8/UuIqIB6nFENg==", + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.0", + "websocket": "^1.0.32" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-shh": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.0.tgz", + "integrity": "sha512-DNRgSa9Jf9xYFUGKSMylrf+zt3MPjhI2qF+UWX07o0y3+uf8zalDGiJOWvIS4upAsdPiKKVJ7co+Neof47OMmg==", + "hasInstallScript": true, + "dependencies": { + "web3-core": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-net": "1.8.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-utils": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.0.tgz", + "integrity": "sha512-7nUIl7UWpLVka2f09CMbKOSEvorvHnaugIabU4mj7zfMvm0tSByLcEu3eyV9qgS11qxxLuOkzBIwCstTflhmpQ==", + "dependencies": { + "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" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3modal": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/web3modal/-/web3modal-1.9.9.tgz", + "integrity": "sha512-ML1C4xH+JTSHHkKbjxuF+f5B3cDUOCnrdQZ8Mlzippq7zRKHf3NBeuIvDdNjtVclJ2S4zYYVmVqRWrgB11ej8A==", + "dependencies": { + "detect-browser": "^5.1.0", + "prop-types": "^15.7.2", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "styled-components": "^5.3.3", + "tslib": "^1.10.0" + } + }, + "node_modules/webidl-conversions": { + "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": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "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-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.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.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", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack/node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/whatwg-encoding": { + "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": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + }, + "node_modules/whatwg-mimetype": { + "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": "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": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" + }, + "node_modules/which-typed-array": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", + "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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/wrap-ansi/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "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", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xhr-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", + "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", + "dependencies": { + "buffer-to-arraybuffer": "^0.0.5", + "object-assign": "^4.1.1", + "query-string": "^5.0.1", + "simple-get": "^2.7.0", + "timed-out": "^4.0.1", + "url-set-query": "^1.0.0", + "xhr": "^2.0.4" + } + }, + "node_modules/xhr-request-promise": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", + "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", + "dependencies": { + "xhr-request": "^1.1.0" + } + }, + "node_modules/xhr2-cookies": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", + "integrity": "sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==", + "dependencies": { + "cookiejar": "^2.1.1" + } + }, + "node_modules/xml-name-validator": { + "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", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/xss": { + "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" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/xss/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==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "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.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/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, + "engines": { + "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", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "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.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.18.6" + } + }, + "@babel/compat-data": { + "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.20.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz", + "integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.2", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.1", + "@babel/parser": "^7.20.2", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + } + }, + "@babel/generator": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.3.tgz", + "integrity": "sha512-Wl5ilw2UD1+ZYprHVprxHZJCFeBWlzZYOovE4SDYLZnqCOD11j+0QzNeEWKLLTWM7nixrZEh7vNIyb76MyJg3A==", + "requires": { + "@babel/types": "^7.20.2", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "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.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "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.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "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.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.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.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.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.18.6", + "regexpu-core": "^5.1.0" + } + }, + "@babel/helper-define-polyfill-provider": { + "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.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-environment-visitor": { + "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.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.18.6" + } + }, + "@babel/helper-function-name": { + "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/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "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.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "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.18.9" + } + }, + "@babel/helper-module-imports": { + "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.18.6" + } + }, + "@babel/helper-module-transforms": { + "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.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.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.18.6" + } + }, + "@babel/helper-plugin-utils": { + "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.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.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.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.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.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.20.2" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "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.18.9" + } + }, + "@babel/helper-split-export-declaration": { + "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.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.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.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.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.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" + } + }, + "@babel/helpers": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", + "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "requires": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.0" + } + }, + "@babel/highlight": { + "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.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", + "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "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.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "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.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.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-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.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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "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.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.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.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "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.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "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.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "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.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "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.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "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.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "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.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.20.1" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "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.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "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.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.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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "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.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.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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@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", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@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", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "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.19.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "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.18.6" + } + }, + "@babel/plugin-transform-async-to-generator": { + "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.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.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.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "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.20.2" + } + }, + "@babel/plugin-transform-classes": { + "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.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.18.9" + } + }, + "@babel/plugin-transform-destructuring": { + "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.20.2" + } + }, + "@babel/plugin-transform-dotall-regex": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "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.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "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.18.6" + } + }, + "@babel/plugin-transform-function-name": { + "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.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "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.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "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.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "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.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "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.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "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.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.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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "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.19.0", + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-new-target": { + "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.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "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.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "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.20.2" + } + }, + "@babel/plugin-transform-property-literals": { + "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.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "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": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "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.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.7.tgz", + "integrity": "sha512-2FoHiSAWkdq4L06uaDN3rS43i6x28desUVxq+zAFuE6kbWYQeiLPJI5IC7Sg9xKYVcrBKSQkVUfH6aeQYbl9QA==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "semver": "^6.3.0" + }, + "dependencies": { + "babel-plugin-polyfill-corejs3": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz", + "integrity": "sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.0", + "core-js-compat": "^3.18.0" + } + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "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.18.6" + } + }, + "@babel/plugin-transform-spread": { + "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.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + } + }, + "@babel/plugin-transform-sticky-regex": { + "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.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "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.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "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.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "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.18.9" + } + }, + "@babel/plugin-transform-unicode-regex": { + "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.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "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", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@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.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.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": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.0.tgz", + "integrity": "sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "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.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@babel/traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", + "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.1", + "@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.1", + "@babel/types": "^7.20.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", + "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@discoveryjs/json-ext": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", + "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "dev": true + }, + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@eslint/eslintrc": { + "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.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "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" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@ethereumjs/common": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz", + "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==", + "requires": { + "crc-32": "^1.2.0", + "ethereumjs-util": "^7.1.5" + } + }, + "@ethereumjs/tx": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz", + "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==", + "requires": { + "@ethereumjs/common": "^2.6.4", + "ethereumjs-util": "^7.1.5" + } + }, + "@ethersproject/abi": { + "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.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.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "requires": { + "@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.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.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.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "requires": { + "@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.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.7.0" + } + }, + "@ethersproject/bignumber": { + "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.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "@ethersproject/bytes": { + "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.7.0" + } + }, + "@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "requires": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "@ethersproject/hash": { + "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.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.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.7.0", + "js-sha3": "0.8.0" + } + }, + "@ethersproject/logger": { + "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.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "requires": { + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/properties": { + "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.7.0" + } + }, + "@ethersproject/rlp": { + "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.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/signing-key": { + "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" + } + }, + "@ethersproject/strings": { + "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.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/transactions": { + "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.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.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "requires": { + "@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.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz", + "integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ==" + }, + "@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 + }, + "@humanwhocodes/config-array": { + "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", + "debug": "^4.1.1", + "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", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "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, + "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, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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 + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "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": "^29.3.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.3.1", + "jest-util": "^29.3.1", + "slash": "^3.0.0" + }, + "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 + }, + "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" + } + } + } + }, + "@jest/core": { + "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": "^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", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "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", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "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 + }, + "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" + } + } + } + }, + "@jest/environment": { + "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": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "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": "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": "^29.3.1", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^29.3.1", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" + } + }, + "@jest/globals": { + "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": "^29.3.1", + "@jest/expect": "^29.3.1", + "@jest/types": "^29.3.1", + "jest-mock": "^29.3.1" + } + }, + "@jest/reporters": { + "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": "^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.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-message-util": "^29.3.1", + "jest-util": "^29.3.1", + "jest-worker": "^29.3.1", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "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" + }, + "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", + "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" + } + } + } + }, + "@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": "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.9" + } + }, + "@jest/test-result": { + "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": "^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": "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": "^29.3.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "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.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": "^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", + "write-file-atomic": "^4.0.1" + }, + "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 + }, + "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 + }, + "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" + } + } + } + }, + "@jest/types": { + "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": "^17.0.8", + "chalk": "^4.0.0" + }, + "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 + }, + "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" + } + } + } + }, + "@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", + "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==" + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "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": "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", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "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": "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": "^2.0.1" + } + }, + "@tarekraafat/autocomplete.js": { + "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==" + }, + "@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, + "optional": true, + "peer": true + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@types/babel__core": { + "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", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "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" + } + }, + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "requires": { + "@types/node": "*" + } + }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/eslint": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@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", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@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", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": 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/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/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", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/prettier": { + "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", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "requires": { + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "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": "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": "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.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.8.0", + "@walletconnect/window-getters": "1.0.0", + "@walletconnect/window-metadata": "1.0.0", + "detect-browser": "5.2.0" + }, + "dependencies": { + "detect-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.0.tgz", + "integrity": "sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA==" + } + } + }, + "@walletconnect/client": { + "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.8.0", + "@walletconnect/iso-crypto": "^1.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" + } + }, + "@walletconnect/core": { + "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.8.0", + "@walletconnect/types": "^1.8.0", + "@walletconnect/utils": "^1.8.0" + } + }, + "@walletconnect/crypto": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/crypto/-/crypto-1.0.2.tgz", + "integrity": "sha512-+OlNtwieUqVcOpFTvLBvH+9J9pntEqH5evpINHfVxff1XIgwV55PpbdvkHu6r9Ib4WQDOFiD8OeeXs1vHw7xKQ==", + "requires": { + "@walletconnect/encoding": "^1.0.1", + "@walletconnect/environment": "^1.0.0", + "@walletconnect/randombytes": "^1.0.2", + "aes-js": "^3.1.2", + "hash.js": "^1.1.7" + } + }, + "@walletconnect/encoding": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/encoding/-/encoding-1.0.1.tgz", + "integrity": "sha512-8opL2rs6N6E3tJfsqwS82aZQDL3gmupWUgmvuZ3CGU7z/InZs3R9jkzH8wmYtpbq0sFK3WkJkQRZFFk4BkrmFA==", + "requires": { + "is-typedarray": "1.0.0", + "typedarray-to-buffer": "3.1.5" + } + }, + "@walletconnect/environment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.0.tgz", + "integrity": "sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ==" + }, + "@walletconnect/http-connection": { + "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.8.0", + "@walletconnect/utils": "^1.8.0", + "eventemitter3": "4.0.7", + "xhr2-cookies": "1.1.0" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + } + } + }, + "@walletconnect/iso-crypto": { + "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.8.0", + "@walletconnect/utils": "^1.8.0" + } + }, + "@walletconnect/jsonrpc-types": { + "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.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.1" + } + }, + "@walletconnect/mobile-registry": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@walletconnect/mobile-registry/-/mobile-registry-1.4.0.tgz", + "integrity": "sha512-ZtKRio4uCZ1JUF7LIdecmZt7FOLnX72RPSY7aUVu7mj7CSfxDwUn6gBuK6WGtH+NZCldBqDl5DenI5fFSvkKYw==" + }, + "@walletconnect/qrcode-modal": { + "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.8.0", + "@walletconnect/mobile-registry": "^1.4.0", + "@walletconnect/types": "^1.8.0", + "copy-to-clipboard": "^3.3.1", + "preact": "10.4.1", + "qrcode": "1.4.4" + } + }, + "@walletconnect/randombytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/randombytes/-/randombytes-1.0.2.tgz", + "integrity": "sha512-ivgOtAyqQnN0rLQmOFPemsgYGysd/ooLfaDA/ACQ3cyqlca56t3rZc7pXfqJOIETx/wSyoF5XbwL+BqYodw27A==", + "requires": { + "@walletconnect/encoding": "^1.0.1", + "@walletconnect/environment": "^1.0.0", + "randombytes": "^2.1.0" + } + }, + "@walletconnect/safe-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.0.tgz", + "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg==" + }, + "@walletconnect/socket-transport": { + "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.8.0", + "@walletconnect/utils": "^1.8.0", + "ws": "7.5.3" + }, + "dependencies": { + "ws": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", + "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", + "requires": {} + } + } + }, + "@walletconnect/types": { + "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.8.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.8.0.tgz", + "integrity": "sha512-zExzp8Mj1YiAIBfKNm5u622oNw44WOESzo6hj+Q3apSMIb0Jph9X3GDIdbZmvVZsNPxWDL7uodKgZcCInZv2vA==", + "requires": { + "@walletconnect/browser-utils": "^1.8.0", + "@walletconnect/encoding": "^1.0.1", + "@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" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "query-string": { + "version": "6.13.5", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.5.tgz", + "integrity": "sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q==", + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" + } + } + }, + "@walletconnect/web3-provider": { + "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" + } + }, + "@walletconnect/window-getters": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.0.tgz", + "integrity": "sha512-xB0SQsLaleIYIkSsl43vm8EwETpBzJ2gnzk7e0wMF3ktqiTGS6TFHxcprMl5R44KKh4tCcHCJwolMCaDSwtAaA==" + }, + "@walletconnect/window-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.0.tgz", + "integrity": "sha512-9eFvmJxIKCC3YWOL97SgRkKhlyGXkrHwamfechmqszbypFspaSk+t2jQXAEU7YClHF6Qjw5eYOmy1//zFi9/GA==", + "requires": { + "@walletconnect/window-getters": "^1.0.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "requires": { + "envinfo": "^7.7.3" + } + }, + "@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "abab": { + "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.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, + "abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "requires": { + "xtend": "~4.0.0" + } + }, + "accepts": { + "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.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "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": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "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": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==" + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.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" + } + }, + "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 + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "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", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "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", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.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==" + } + } + }, + "assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "requires": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "requires": { + "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", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "async-mutex": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", + "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "autoprefixer": { + "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.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" + } + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "babel-jest": { + "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": "^29.3.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.2.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "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 + }, + "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" + } + } + } + }, + "babel-loader": { + "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": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "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.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-plugin-polyfill-corejs2": { + "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.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "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.3", + "core-js-compat": "^3.25.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + } + }, + "babel-plugin-styled-components": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.3.tgz", + "integrity": "sha512-meGStRGv+VuKA/q0/jXxrPNWEm4LPfYIqxooDTdmh8kFsP/Ph7jJG5rUPwUPX3QHUvggwdbgdGpo88P/rRYsVw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.15.4", + "@babel/helper-module-imports": "^7.15.4", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "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": "^29.2.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", + "requires": { + "precond": "0.2" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "bignumber.js": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", + "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "blakejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz", + "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==" + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bn.js": { + "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.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "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": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "bootstrap": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", + "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", + "requires": {} + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "browserslist": { + "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.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "buffer-to-arraybuffer": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", + "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "bufferutil": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", + "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "peer": true, + "requires": { + "semver": "^7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "bytes": { + "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": "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": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "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", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "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", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "chart.js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", + "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" + }, + "chartjs-adapter-luxon": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.2.0.tgz", + "integrity": "sha512-h1lEns7+8cUN/Dmk24dhrT9hpAimKImQxzHpILqXn2kocdzj9b/fDlBa8v8/OMq5rq0uZEx/NV1WpByH4l2/Rw==", + "requires": {} + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + }, + "checkpoint-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz", + "integrity": "sha1-BOTLUWuRQziTWB5tRgGnjpVS6gY=", + "requires": { + "functional-red-black-tree": "^1.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "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", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "ci-info": { + "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": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", + "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", + "requires": { + "buffer": "^5.5.0", + "class-is": "^1.1.0", + "multibase": "~0.6.0", + "multicodec": "^1.0.0", + "multihashes": "~0.4.15" + }, + "dependencies": { + "multicodec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", + "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", + "requires": { + "buffer": "^5.6.0", + "varint": "^5.0.0" + } + } + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "class-is": { + "version": "1.1.0", + "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.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", + "tiny-emitter": "^2.0.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "clone-response": { + "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" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "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.2", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", + "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", + "dev": true + }, + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "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", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "content-hash": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", + "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", + "requires": { + "cids": "^0.7.1", + "multicodec": "^0.5.5", + "multihashes": "^0.4.15" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "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": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + }, + "copy-to-clipboard": { + "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": "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.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + } + }, + "core-js": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", + "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==" + }, + "core-js-compat": { + "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.21.3" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "crc-32": { + "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", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "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==" + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-fetch": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.6.tgz", + "integrity": "sha512-9JZz+vXCmfKUZ68zAptS7k4Nu8e2qcibe7WVZYps7sAgk5R8GYTc+T1WR0v1rlP9HxgARmOX1UTIJZFytajpNA==", + "requires": { + "node-fetch": "^2.6.7", + "whatwg-fetch": "^2.0.4" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, + "css-declaration-sorter": { + "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": {} + }, + "css-loader": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", + "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "css-minimizer-webpack-plugin": { + "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.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": { + "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.1.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.1.2.tgz", + "integrity": "sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.1.2", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "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" + } + } + } + }, + "css-select": { + "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": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "css-what": { + "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": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" + }, + "cssnano": { + "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.2.12", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "cssnano-preset-default": { + "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": "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": {} + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + } + }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "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.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "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.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=" + }, + "decompress-response": { + "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": "^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": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "defer-to-connect": { + "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", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "requires": { + "abstract-leveldown": "~2.6.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegate": { + "version": "3.2.0", + "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=", + "dev": true, + "optional": true, + "peer": true + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "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", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz", + "integrity": "sha512-eAcRiEPTs7utXWPaAgu/OX1HRJpxW7xSHpw4LTDrGFaeWnJ37HRlqpUkKsDm0AoTbtrvHQhH+5U2Cd87EGhJTg==" + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "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": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.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==" + } + } + }, + "dijkstrajs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz", + "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serializer": { + "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", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domexception": { + "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": "^7.0.0" + } + }, + "domhandler": { + "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" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "dropzone": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", + "integrity": "sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA==" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "electron-to-chromium": { + "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", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "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==" + } + } + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "peer": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "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==", + "optional": true, + "peer": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "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", + "tapable": "^2.2.0" + } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "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", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "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.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-object-assign": { + "version": "1.1.0", + "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", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "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": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.27.0.tgz", + "integrity": "sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==", + "dev": true, + "requires": { + "@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", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^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", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "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" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "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 + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "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", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "requires": { + "type-fest": "^0.20.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 + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "peer": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-n": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.2.2.tgz", + "integrity": "sha512-MLjZVAv4TiCIoXqjibNqCJjLkGHfrOY3XZ0ZBLoW0OnS3o98PUBnzB/kfp8dCz/4A4Y18jjX50PRnqI4ACFY1Q==", + "dev": true, + "peer": true, + "requires": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.9.0", + "minimatch": "^3.1.2", + "resolve": "^1.10.1", + "semver": "^7.3.7" + }, + "dependencies": { + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "peer": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "peer": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + } + } + } + }, + "eslint-plugin-promise": { + "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": {} + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "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.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "eth-block-tracker": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.4.3.tgz", + "integrity": "sha512-A8tG4Z4iNg4mw5tP1Vung9N9IjgMNqpiMoJ/FouSFwNCGHv2X0mmOYwtQOJzki6XN7r7Tyo01S29p7b224I4jw==", + "requires": { + "@babel/plugin-transform-runtime": "^7.5.5", + "@babel/runtime": "^7.5.5", + "eth-query": "^2.1.0", + "json-rpc-random-id": "^1.0.1", + "pify": "^3.0.0", + "safe-event-emitter": "^1.0.1" + } + }, + "eth-ens-namehash": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", + "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", + "requires": { + "idna-uts46-hx": "^2.3.1", + "js-sha3": "^0.5.7" + }, + "dependencies": { + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==" + } + } + }, + "eth-json-rpc-filters": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz", + "integrity": "sha512-DGtqpLU7bBg63wPMWg1sCpkKCf57dJ+hj/k3zF26anXMzkmtSBDExL8IhUu7LUd34f0Zsce3PYNO2vV2GaTzaw==", + "requires": { + "@metamask/safe-event-emitter": "^2.0.0", + "async-mutex": "^0.2.6", + "eth-json-rpc-middleware": "^6.0.0", + "eth-query": "^2.1.2", + "json-rpc-engine": "^6.1.0", + "pify": "^5.0.0" + }, + "dependencies": { + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + } + } + }, + "eth-json-rpc-infura": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-5.1.0.tgz", + "integrity": "sha512-THzLye3PHUSGn1EXMhg6WTLW9uim7LQZKeKaeYsS9+wOBcamRiCQVGHa6D2/4P0oS0vSaxsBnU/J6qvn0MPdow==", + "requires": { + "eth-json-rpc-middleware": "^6.0.0", + "eth-rpc-errors": "^3.0.0", + "json-rpc-engine": "^5.3.0", + "node-fetch": "^2.6.0" + }, + "dependencies": { + "json-rpc-engine": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz", + "integrity": "sha512-rAffKbPoNDjuRnXkecTjnsE3xLLrb00rEkdgalINhaYVYIxDwWtvYBr9UFbhTvPB1B2qUOLoFd/cV6f4Q7mh7g==", + "requires": { + "eth-rpc-errors": "^3.0.0", + "safe-event-emitter": "^1.0.1" + } + } + } + }, + "eth-json-rpc-middleware": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-6.0.0.tgz", + "integrity": "sha512-qqBfLU2Uq1Ou15Wox1s+NX05S9OcAEL4JZ04VZox2NS0U+RtCMjSxzXhLFWekdShUPZ+P8ax3zCO2xcPrp6XJQ==", + "requires": { + "btoa": "^1.2.1", + "clone": "^2.1.1", + "eth-query": "^2.1.2", + "eth-rpc-errors": "^3.0.0", + "eth-sig-util": "^1.4.2", + "ethereumjs-util": "^5.1.2", + "json-rpc-engine": "^5.3.0", + "json-stable-stringify": "^1.0.1", + "node-fetch": "^2.6.1", + "pify": "^3.0.0", + "safe-event-emitter": "^1.0.1" + }, + "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==" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "json-rpc-engine": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz", + "integrity": "sha512-rAffKbPoNDjuRnXkecTjnsE3xLLrb00rEkdgalINhaYVYIxDwWtvYBr9UFbhTvPB1B2qUOLoFd/cV6f4Q7mh7g==", + "requires": { + "eth-rpc-errors": "^3.0.0", + "safe-event-emitter": "^1.0.1" + } + } + } + }, + "eth-lib": { + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz", + "integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "nano-json-stream-parser": "^0.1.2", + "servify": "^0.1.12", + "ws": "^3.0.0", + "xhr-request-promise": "^0.1.2" + }, + "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==" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "eth-net-props": { + "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" + } + }, + "eth-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz", + "integrity": "sha1-1nQdkAAQa1FRDHLbktY2VFam2l4=", + "requires": { + "json-rpc-random-id": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "eth-rpc-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-3.0.0.tgz", + "integrity": "sha512-iPPNHPrLwUlR9xCSYm7HHQjWBasor3+KZfRvwEWxMz3ca0yqnlBeJrnyphkGIXZ4J7AMAaOLmwy4AWhnxOiLxg==", + "requires": { + "fast-safe-stringify": "^2.0.6" + } + }, + "eth-sig-util": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", + "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", + "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "ethereumjs-util": "^5.1.1" + }, + "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==" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereum-bloom-filters": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", + "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "requires": { + "js-sha3": "^0.8.0" + } + }, + "ethereum-common": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", + "integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==" + }, + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "ethereumjs-abi": { + "version": "git+ssh://git@github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0", + "from": "ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git", + "requires": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.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==" + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "ethereumjs-account": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-2.0.5.tgz", + "integrity": "sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA==", + "requires": { + "ethereumjs-util": "^5.0.0", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + }, + "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==" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-block": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz", + "integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==", + "requires": { + "async": "^2.0.1", + "ethereum-common": "0.2.0", + "ethereumjs-tx": "^1.2.2", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "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==" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz", + "integrity": "sha512-hTfZjwGX52GS2jcVO6E2sx4YuFnf0Fhp5ylo4pEPhEffNln7vS59Hr5sLnp3/QCazFLluuBZ+FZ6J5HTp0EqCA==" + }, + "ethereumjs-tx": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", + "integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==", + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.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==" + }, + "ethereum-common": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz", + "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-util": { + "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", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "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": "*" + } + } + } + }, + "ethereumjs-vm": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz", + "integrity": "sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw==", + "requires": { + "async": "^2.1.2", + "async-eventemitter": "^0.2.2", + "ethereumjs-account": "^2.0.3", + "ethereumjs-block": "~2.2.0", + "ethereumjs-common": "^1.1.0", + "ethereumjs-util": "^6.0.0", + "fake-merkle-patricia-tree": "^1.0.1", + "functional-red-black-tree": "^1.0.1", + "merkle-patricia-tree": "^2.3.2", + "rustbn.js": "~0.2.0", + "safe-buffer": "^5.1.1" + }, + "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==" + }, + "ethereumjs-block": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz", + "integrity": "sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==", + "requires": { + "async": "^2.0.1", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.1", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-tx": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", + "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", + "requires": { + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.0.0" + } + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", + "requires": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" + } + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + }, + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "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/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.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "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.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "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": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "requires": { + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fake-merkle-patricia-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz", + "integrity": "sha1-S4w6z7Ugr635hgsfFM2M40As3dM=", + "requires": { + "checkpoint-store": "^1.1.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "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" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "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.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "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, + "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, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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 + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "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", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "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", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "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", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "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": "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", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": 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" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "dependencies": { + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + } + } + }, + "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", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, + "got": { + "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": { + "version": "4.2.9", + "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", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "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", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "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-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "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", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "highlight.js": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", + "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==" + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "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": "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": "^2.0.0" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http-errors": { + "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": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "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": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==" + }, + "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, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "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.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", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "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", + "integrity": "sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, + "idna-uts46-hx": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", + "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", + "requires": { + "punycode": "2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==" + } + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, + "immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", + "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "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, + "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, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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 + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "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", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "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==" + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "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-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fn/-/is-fn-1.0.0.tgz", + "integrity": "sha1-lUPV3nvPWwiiLsiiC65uKG1RDYw=" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hex-prefixed": { + "version": "1.0.0", + "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", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "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-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "optional": true, + "peer": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", + "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-weakref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", + "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "requires": { + "call-bind": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "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", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.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" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "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" + } + }, + "jest": { + "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": "^29.3.1", + "@jest/types": "^29.3.1", + "import-local": "^3.0.2", + "jest-cli": "^29.3.1" + } + }, + "jest-changed-files": { + "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": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "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": "^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", + "is-generator-fn": "^2.0.0", + "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" + }, + "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 + }, + "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" + } + } + } + }, + "jest-cli": { + "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": "^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.9", + "import-local": "^3.0.2", + "jest-config": "^29.3.1", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "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 + }, + "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" + } + } + } + }, + "jest-config": { + "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.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.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", + "parse-json": "^5.2.0", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "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 + }, + "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" + } + } + } + }, + "jest-diff": { + "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": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" + }, + "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 + }, + "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" + } + } + } + }, + "jest-docblock": { + "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": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", + "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", + "dev": true, + "requires": { + "@jest/types": "^29.3.1", + "chalk": "^4.0.0", + "jest-get-type": "^29.2.0", + "jest-util": "^29.3.1", + "pretty-format": "^29.3.1" + }, + "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 + }, + "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" + } + } + } + }, + "jest-environment-jsdom": { + "version": "29.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.3.0.tgz", + "integrity": "sha512-xFLbMR4OF4lntNcO9LthJdPRbI9WgfFlG73aQS6wQ54+v4oSAp8T4FKUw0add+Z+Ghu/dirRxuvc4FzzN5kRxw==", + "dev": true, + "requires": { + "@jest/environment": "^29.3.0", + "@jest/fake-timers": "^29.3.0", + "@jest/types": "^29.2.1", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.3.0", + "jest-util": "^29.2.1", + "jsdom": "^20.0.0" + } + }, + "jest-environment-node": { + "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": "^29.3.1", + "@jest/fake-timers": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "jest-mock": "^29.3.1", + "jest-util": "^29.3.1" + } + }, + "jest-get-type": { + "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": "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": "^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.9", + "jest-regex-util": "^29.2.0", + "jest-util": "^29.3.1", + "jest-worker": "^29.3.1", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "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 + }, + "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": "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" + } + } + } + }, + "jest-leak-detector": { + "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": "^29.2.0", + "pretty-format": "^29.3.1" + } + }, + "jest-matcher-utils": { + "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": "^29.3.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.3.1" + }, + "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 + }, + "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" + } + } + } + }, + "jest-message-util": { + "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": "^29.3.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "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 + }, + "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" + } + } + } + }, + "jest-mock": { + "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": "^29.3.1", + "@types/node": "*", + "jest-util": "^29.3.1" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "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": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", + "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.3.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.3.1", + "jest-validate": "^29.3.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "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 + }, + "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" + } + } + } + }, + "jest-resolve-dependencies": { + "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-regex-util": "^29.2.0", + "jest-snapshot": "^29.3.1" + } + }, + "jest-runner": { + "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": "^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.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": { + "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" + }, + "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", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "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", + "glob": "^7.1.3", + "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" + }, + "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 + }, + "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" + } + } + } + }, + "jest-snapshot": { + "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.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.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": "^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": "^29.3.1", + "semver": "^7.3.5" + }, + "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 + }, + "semver": { + "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" + } + }, + "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" + } + } + } + }, + "jest-util": { + "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": "^29.3.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "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 + }, + "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" + } + } + } + }, + "jest-validate": { + "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": "^29.3.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.2.0", + "leven": "^3.1.0", + "pretty-format": "^29.3.1" + }, + "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" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "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 + }, + "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" + } + } + } + }, + "jest-watcher": { + "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": "^29.3.1", + "@jest/types": "^29.3.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.3.1", + "string-length": "^4.0.1" + }, + "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 + }, + "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" + } + } + } + }, + "jest-worker": { + "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": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.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": "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" + } + } + } + }, + "jquery": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + }, + "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", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "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.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.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": "^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": "^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": { + "@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": "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" + } + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-buffer": { + "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", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-rpc-engine": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", + "integrity": "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==", + "requires": { + "@metamask/safe-event-emitter": "^2.0.0", + "eth-rpc-errors": "^4.0.2" + }, + "dependencies": { + "eth-rpc-errors": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", + "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", + "requires": { + "fast-safe-stringify": "^2.0.6" + } + } + } + }, + "json-rpc-random-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", + "integrity": "sha1-uknZat7RRE27jaPSA3SKy7zeyMg=" + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "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": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "keccak": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", + "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + } + }, + "keyv": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", + "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "keyvaluestorage-interface": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", + "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true + }, + "level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==" + }, + "level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "requires": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "requires": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + }, + "dependencies": { + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + } + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lilconfig": { + "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": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "dev": true + }, + "loader-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", + "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.differenceby": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash.differenceby/-/lodash.differenceby-4.8.0.tgz", + "integrity": "sha1-z9WelDU69d5R2l0wLKTr/zP6rFc=" + }, + "lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=" + }, + "lodash.first": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.first/-/lodash.first-3.0.0.tgz", + "integrity": "sha1-Xa4YDX+BjuZfxbIQsQSnu++YoWo=" + }, + "lodash.forin": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.forin/-/lodash.forin-4.4.0.tgz", + "integrity": "sha1-XT8grlZAEfvog4H32YlJyclRlzE=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.intersectionby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.intersectionby/-/lodash.intersectionby-4.7.0.tgz", + "integrity": "sha1-EvEl5NoAsiKQ/r2htsG2i7klUSU=" + }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + }, + "lodash.keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz", + "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=" + }, + "lodash.last": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", + "integrity": "sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw=" + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + }, + "lodash.max": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.max/-/lodash.max-4.0.1.tgz", + "integrity": "sha1-hzVWbGGLNan3YFILSHrnllivE2o=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.min": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.min/-/lodash.min-4.0.1.tgz", + "integrity": "sha1-SsG5qLr4ttKKaQ1xZRJRDPwUcIw=" + }, + "lodash.noop": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", + "integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=" + }, + "lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=" + }, + "lodash.rangeright": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.rangeright/-/lodash.rangeright-4.2.0.tgz", + "integrity": "sha1-dCrF5C+R9oKiwLaHwpt52TIzkEI=" + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lowercase-keys": { + "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", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" + }, + "luxon": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.0.tgz", + "integrity": "sha512-7w6hmKC0/aoWnEsmPCu5Br54BmbmUp5GfcqBxQngRcXJ+q5fdfjEzn7dxmJh2YdDhgW8PccYtlWKSv4tQkrTQg==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "requires": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "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": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "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==", + "requires": { + "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" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "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==" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "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==", + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "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==" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "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.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.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "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.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "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==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.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", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "mkdirp-promise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", + "integrity": "sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==", + "requires": { + "mkdirp": "*" + } + }, + "mock-fs": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", + "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" + }, + "moment": { + "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", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multibase": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", + "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + }, + "multicodec": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", + "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", + "requires": { + "varint": "^5.0.0" + } + }, + "multihashes": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", + "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", + "requires": { + "buffer": "^5.5.0", + "multibase": "^0.7.0", + "varint": "^5.0.0" + }, + "dependencies": { + "multibase": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", + "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + } + } + }, + "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": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==" + }, + "nanoassert": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", + "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "nanomorph": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/nanomorph/-/nanomorph-5.4.3.tgz", + "integrity": "sha512-uPP5y0x21KISffZCKHh1A0QW0RHZFQS0BR7LetlHBlay6UWAbjwhjiJTxOO6JeMHko5Cigl617zFoGrYFJ8ZLg==", + "requires": { + "nanoassert": "^1.1.0" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "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", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "next-tick": { + "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", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "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", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" + }, + "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-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-sass": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", + "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", + "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.1", + "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 + } + } + }, + "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 + }, + "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 + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "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.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" + } + }, + "number-to-bn": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", + "requires": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" + } + } + }, + "numeral": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", + "integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=" + }, + "nwsapi": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "oboe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", + "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", + "requires": { + "http-https": "^1.0.0" + } + }, + "on-finished": { + "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" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "p-cancelable": { + "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", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "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-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-headers": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.4.tgz", + "integrity": "sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw==" + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "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", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "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=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-parser": { + "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": "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": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" + }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "phoenix": { + "version": "file:../../../deps/phoenix" + }, + "phoenix_html": { + "version": "file:../../../deps/phoenix_html" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pikaday": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz", + "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew==" + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, + "postcss": { + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-calc": { + "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.9", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-colormin": { + "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.2.0" + } + }, + "postcss-convert-values": { + "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": { + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-discard-comments": { + "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.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.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.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": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "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": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" + } + }, + "postcss-merge-rules": { + "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": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-minify-font-values": { + "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.2.0" + } + }, + "postcss-minify-gradients": { + "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": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-params": { + "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": { + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-selectors": { + "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": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-normalize-charset": { + "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.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": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-positions": { + "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.2.0" + } + }, + "postcss-normalize-repeat-style": { + "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": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-string": { + "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.2.0" + } + }, + "postcss-normalize-timing-functions": { + "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": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-unicode": { + "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.6", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-url": { + "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": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-whitespace": { + "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.2.0" + } + }, + "postcss-ordered-values": { + "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": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-reduce-initial": { + "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.6", + "caniuse-api": "^3.0.0" + } + }, + "postcss-reduce-transforms": { + "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": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-selector-parser": { + "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", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "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.2.0", + "svgo": "^2.7.0" + } + }, + "postcss-unique-selectors": { + "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": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "preact": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.4.1.tgz", + "integrity": "sha512-WKrRpCSwL2t3tpOOGhf2WfTpcmbpxaWtDbdJdKdjd0aEiTkvOmS4NBkG6kzlaAHI9AkQ3iVqbFWM3Ei7mZ4o1Q==" + }, + "precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "pretty-format": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", + "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "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", + "integrity": "sha1-XSp0kBC/tn2WNZj805YHRqaP7vc=", + "requires": { + "is-fn": "^1.0.0", + "set-immediate-shim": "^1.0.1" + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "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==" + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qrcode": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz", + "integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==", + "requires": { + "buffer": "^5.4.3", + "buffer-alloc": "^1.2.0", + "buffer-from": "^1.1.1", + "dijkstrajs": "^1.0.1", + "isarray": "^2.0.1", + "pngjs": "^3.3.0", + "yargs": "^13.2.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "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": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "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", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "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.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } + }, + "react-is": { + "version": "16.13.1", + "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", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "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==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "reduce-reducers": { + "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.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" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "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" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "regenerator-transform": { + "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" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "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": "^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.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "regjsparser": { + "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" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "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==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "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", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "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", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "requires": { + "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", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "requires": { + "bn.js": "^5.2.0" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-event-emitter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz", + "integrity": "sha512-e1wFe99A91XYYxoQbcq2ZJUWurxEyP8vfz7A7vuUe1s95q8r5ebraVaA1BukYJcpM6V16ugWoD9vngi8Ccu5fg==", + "requires": { + "events": "^3.0.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sass": { + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.0.tgz", + "integrity": "sha512-WFJ9XrpkcnqZcYuLRJh5qiV6ibQOR4AezleeEjTjMsCocYW59dEG19U3fwTTXxzi2Ed3yjPBp727hbbj53pHFw==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-graph": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", + "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.17.11", + "scss-tokenizer": "^0.4.3", + "yargs": "^17.2.1" + } + }, + "sass-loader": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.1.0.tgz", + "integrity": "sha512-tZS1RJQ2n2+QNyf3CCAo1H562WjL/5AM6Gi8YcPVVoNxQX8d19mx8E+8fRrMWsyc93ZL6Q8vZDSM0FHVTJaVnQ==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, + "saxes": { + "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" + } + }, + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "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" + }, + "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": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, + "scss-tokenizer": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", + "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "js-base64": "^2.4.9", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "search-params": { + "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", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "requires": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, + "semaphore": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", + "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "send": { + "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": "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": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "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", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-static": { + "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.18.0" + } + }, + "servify": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", + "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", + "requires": { + "body-parser": "^1.16.0", + "cors": "^2.8.1", + "express": "^4.14.0", + "request": "^2.79.0", + "xhr": "^2.3.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "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": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz", + "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", + "requires": { + "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": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "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.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", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "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", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "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", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "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" + } + } + } + }, + "stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "requires": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", + "requires": { + "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", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true, + "requires": {} + }, + "styled-components": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz", + "integrity": "sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^0.8.8", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.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": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + } + }, + "swarm-js": { + "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": "^11.8.5", + "mime-types": "^2.1.16", + "mkdirp-promise": "^5.0.1", + "mock-fs": "^4.1.0", + "setimmediate": "^1.0.5", + "tar": "^4.0.2", + "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", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "got": { + "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": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "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", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "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.6" + } + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "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", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "requires": { + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "sweetalert2": { + "version": "11.6.7", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.6.7.tgz", + "integrity": "sha512-nbsmUtfTodJvZ873sn61U0brrHtn8xi8wfn+bVVc9IV05fgOdDMhAhdQ2zQJBYsvggbbp46ZFHMr0/fZJnZPtQ==" + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "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" + } + }, + "terser": { + "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-support": "~0.5.20" + }, + "dependencies": { + "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 + } + } + }, + "terser-webpack-plugin": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", + "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", + "dev": true, + "requires": { + "jest-worker": "^27.0.6", + "p-limit": "^3.1.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.2" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==" + }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tough-cookie": { + "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.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": "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", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "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" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "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", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "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", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "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": { + "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": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==" + }, + "utf-8-validate": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", + "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, + "util": { + "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", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "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-to-istanbul": { + "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" + } + }, + "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" + } + }, + "varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "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": "^4.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "watchpack": { + "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", + "graceful-fs": "^4.1.2" + } + }, + "web3": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.0.tgz", + "integrity": "sha512-sldr9stK/SALSJTgI/8qpnDuBJNMGjVR84hJ+AcdQ+MLBGLMGsCDNubCoyO6qgk1/Y9SQ7ignegOI/7BPLoiDA==", + "requires": { + "web3-bzz": "1.8.0", + "web3-core": "1.8.0", + "web3-eth": "1.8.0", + "web3-eth-personal": "1.8.0", + "web3-net": "1.8.0", + "web3-shh": "1.8.0", + "web3-utils": "1.8.0" + } + }, + "web3-bzz": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.0.tgz", + "integrity": "sha512-caDtdKeLi7+2Vb+y+cq2yyhkNjnxkFzVW0j1DtemarBg3dycG1iEl75CVQMLNO6Wkg+HH9tZtRnUyFIe5LIUeQ==", + "requires": { + "@types/node": "^12.12.6", + "got": "12.1.0", + "swarm-js": "^0.1.40" + }, + "dependencies": { + "@types/node": { + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.0.tgz", + "integrity": "sha512-9sCA+Z02ci6zoY2bAquFiDjujRwmSKHiSGi4B8IstML8okSytnzXk1izHYSynE7ahIkguhjWAuXFvX76F5rAbA==", + "requires": { + "@types/bn.js": "^5.1.0", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-requestmanager": "1.8.0", + "web3-utils": "1.8.0" + }, + "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.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.8.0", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.0.tgz", + "integrity": "sha512-nMAVwZB3rEp/khHI2BvFy0e/xCryf501p5NGjswmJtEM+Zrd3Biaw52JrB1qAZZIzCA8cmLKaOgdfamoDOpWdw==", + "requires": { + "web3-eth-iban": "1.8.0", + "web3-utils": "1.8.0" + } + }, + "web3-core-method": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.0.tgz", + "integrity": "sha512-c94RAzo3gpXwf2rf8rL8C77jOzNWF4mXUoUfZYYsiY35cJFd46jQDPI00CB5+ZbICTiA5mlVzMj4e7jAsTqiLA==", + "requires": { + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.0", + "web3-core-promievent": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-utils": "1.8.0" + } + }, + "web3-core-promievent": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.0.tgz", + "integrity": "sha512-FGLyjAuOaAQ+ZhV6iuw9tg/9WvIkSZXKHQ4mdTyQ8MxVraOtFivOCbuLLsGgapfHYX+RPxsc1j1YzQjKoupagQ==", + "requires": { + "eventemitter3": "4.0.4" + } + }, + "web3-core-requestmanager": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.0.tgz", + "integrity": "sha512-2AoYCs3Owl5foWcf4uKPONyqFygSl9T54L8b581U16nsUirjhoTUGK/PBhMDVcLCmW4QQmcY5A8oPFpkQc1TTg==", + "requires": { + "util": "^0.12.0", + "web3-core-helpers": "1.8.0", + "web3-providers-http": "1.8.0", + "web3-providers-ipc": "1.8.0", + "web3-providers-ws": "1.8.0" + } + }, + "web3-core-subscriptions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.0.tgz", + "integrity": "sha512-7lHVRzDdg0+Gcog55lG6Q3D8JV+jN+4Ly6F8cSn9xFUAwOkdbgdWsjknQG7t7CDWy21DQkvdiY2BJF8S68AqOA==", + "requires": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.0" + } + }, + "web3-eth": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.0.tgz", + "integrity": "sha512-hist52os3OT4TQFB/GxPSMxTh3995sz6LPvQpPvj7ktSbpg9RNSFaSsPlCT63wUAHA3PZb1FemkAIeQM5t72Lw==", + "requires": { + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-eth-abi": "1.8.0", + "web3-eth-accounts": "1.8.0", + "web3-eth-contract": "1.8.0", + "web3-eth-ens": "1.8.0", + "web3-eth-iban": "1.8.0", + "web3-eth-personal": "1.8.0", + "web3-net": "1.8.0", + "web3-utils": "1.8.0" + } + }, + "web3-eth-abi": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.0.tgz", + "integrity": "sha512-xPeMb2hS9YLQK/Q5YZpkcmzoRGM+/R8bogSrYHhNC3hjZSSU0YRH+1ZKK0f9YF4qDZaPMI8tKWIMSCDIpjG6fg==", + "requires": { + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.0" + } + }, + "web3-eth-accounts": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.0.tgz", + "integrity": "sha512-HQ/MDSv4bexwJLvnqsM6xpGE7c2NVOqyhzOZFyMUKXbIwIq85T3TaLnM9pCN7XqMpDcfxqiZ3q43JqQVkzHdmw==", + "requires": { + "@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.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-utils": "1.8.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==" + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "web3-eth-contract": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.0.tgz", + "integrity": "sha512-6xeXhW2YoCrz2Ayf2Vm4srWiMOB6LawkvxWJDnUWJ8SMATg4Pgu42C/j8rz/enXbYWt2IKuj0kk8+QszxQbK+Q==", + "requires": { + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-promievent": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-eth-abi": "1.8.0", + "web3-utils": "1.8.0" + }, + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.0.tgz", + "integrity": "sha512-/eFbQEwvsMOEiOhw9/iuRXCsPkqAmHHWuFOrThQkozRgcnSTRnvxkkRC/b6koiT5/HaKeUs4yQDg+/ixsIxZxA==", + "requires": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-promievent": "1.8.0", + "web3-eth-abi": "1.8.0", + "web3-eth-contract": "1.8.0", + "web3-utils": "1.8.0" + } + }, + "web3-eth-iban": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.0.tgz", + "integrity": "sha512-4RbvUxcMpo/e5811sE3a6inJ2H4+FFqUVmlRYs0RaXaxiHweahSRBNcpO0UWgmlePTolj0rXqPT2oEr0DuC8kg==", + "requires": { + "bn.js": "^5.2.1", + "web3-utils": "1.8.0" + } + }, + "web3-eth-personal": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.0.tgz", + "integrity": "sha512-L7FT4nR3HmsfZyIAhFpEctKkYGOjRC2h6iFKs9gnFCHZga8yLcYcGaYOBIoYtaKom99MuGBoosayWt/Twh7F5A==", + "requires": { + "@types/node": "^12.12.6", + "web3-core": "1.8.0", + "web3-core-helpers": "1.8.0", + "web3-core-method": "1.8.0", + "web3-net": "1.8.0", + "web3-utils": "1.8.0" + }, + "dependencies": { + "@types/node": { + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.0.tgz", + "integrity": "sha512-kX6EAacK7QrOe7DOh0t5yHS5q2kxZmTCxPVwSz9io9xBeE4n4UhmzGJ/VfhP2eM3OPKYeypcR3LEO6zZ8xn2vw==", + "requires": { + "web3-core": "1.8.0", + "web3-core-method": "1.8.0", + "web3-utils": "1.8.0" + } + }, + "web3-provider-engine": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-16.0.1.tgz", + "integrity": "sha512-/Eglt2aocXMBiDj7Se/lyZnNDaHBaoJlaUfbP5HkLJQC/HlGbR+3/W+dINirlJDhh7b54DzgykqY7ksaU5QgTg==", + "requires": { + "async": "^2.5.0", + "backoff": "^2.5.0", + "clone": "^2.0.0", + "cross-fetch": "^2.1.0", + "eth-block-tracker": "^4.4.2", + "eth-json-rpc-filters": "^4.2.1", + "eth-json-rpc-infura": "^5.1.0", + "eth-json-rpc-middleware": "^6.0.0", + "eth-rpc-errors": "^3.0.0", + "eth-sig-util": "^1.4.2", + "ethereumjs-block": "^1.2.2", + "ethereumjs-tx": "^1.2.0", + "ethereumjs-util": "^5.1.5", + "ethereumjs-vm": "^2.3.4", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "readable-stream": "^2.2.9", + "request": "^2.85.0", + "semaphore": "^1.0.3", + "ws": "^5.1.1", + "xhr": "^2.2.0", + "xtend": "^4.0.1" + }, + "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==" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "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==", + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "ws": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", + "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "web3-providers-http": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.0.tgz", + "integrity": "sha512-/MqxwRzExohBWW97mqlCSW/+NHydGRyoEDUS1bAIF2YjfKFwyRtHgrEzOojzkC9JvB+8LofMvbXk9CcltpZapw==", + "requires": { + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.0" + }, + "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.8.0", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.0.tgz", + "integrity": "sha512-tAXHtVXNUOgehaBU8pzAlB3qhjn/PRpjdzEjzHNFqtRRTwzSEKOJxFeEhaUA4FzHnTlbnrs8ujHWUitcp1elfg==", + "requires": { + "oboe": "2.1.5", + "web3-core-helpers": "1.8.0" + } + }, + "web3-providers-ws": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.0.tgz", + "integrity": "sha512-bcZtSifsqyJxwkfQYamfdIRp4nhj9eJd7cxHg1uUkfLJK125WP96wyJL1xbPt7qt0MpfnTFn8/UuIqIB6nFENg==", + "requires": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.0", + "websocket": "^1.0.32" + } + }, + "web3-shh": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.0.tgz", + "integrity": "sha512-DNRgSa9Jf9xYFUGKSMylrf+zt3MPjhI2qF+UWX07o0y3+uf8zalDGiJOWvIS4upAsdPiKKVJ7co+Neof47OMmg==", + "requires": { + "web3-core": "1.8.0", + "web3-core-method": "1.8.0", + "web3-core-subscriptions": "1.8.0", + "web3-net": "1.8.0" + } + }, + "web3-utils": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.0.tgz", + "integrity": "sha512-7nUIl7UWpLVka2f09CMbKOSEvorvHnaugIabU4mj7zfMvm0tSByLcEu3eyV9qgS11qxxLuOkzBIwCstTflhmpQ==", + "requires": { + "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" + } + }, + "web3modal": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/web3modal/-/web3modal-1.9.9.tgz", + "integrity": "sha512-ML1C4xH+JTSHHkKbjxuF+f5B3cDUOCnrdQZ8Mlzippq7zRKHf3NBeuIvDdNjtVclJ2S4zYYVmVqRWrgB11ej8A==", + "requires": { + "detect-browser": "^5.1.0", + "prop-types": "^15.7.2", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "styled-components": "^5.3.3", + "tslib": "^1.10.0" + } + }, + "webidl-conversions": { + "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.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "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-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.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + } + } + }, + "webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "requires": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "whatwg-encoding": { + "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.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": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + }, + "whatwg-mimetype": { + "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": "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": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" + }, + "which-typed-array": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", + "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0", + "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", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "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" + } + }, + "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 + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "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", + "signal-exit": "^3.0.7" + } + }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "requires": {} + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xhr-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", + "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", + "requires": { + "buffer-to-arraybuffer": "^0.0.5", + "object-assign": "^4.1.1", + "query-string": "^5.0.1", + "simple-get": "^2.7.0", + "timed-out": "^4.0.1", + "url-set-query": "^1.0.0", + "xhr": "^2.0.4" + } + }, + "xhr-request-promise": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", + "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", + "requires": { + "xhr-request": "^1.1.0" + } + }, + "xhr2-cookies": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", + "integrity": "sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==", + "requires": { + "cookiejar": "^2.1.1" + } + }, + "xml-name-validator": { + "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": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "xss": { + "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" + }, + "dependencies": { + "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==" + } + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "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.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "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 + }, + "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, + "optional": true, + "peer": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json new file mode 100644 index 0000000..ef1f6df --- /dev/null +++ b/apps/block_scout_web/assets/package.json @@ -0,0 +1,110 @@ +{ + "repository": { + "type": "git", + "url": "git+https://github.com/blockscout/blockscout.git" + }, + "private": true, + "name": "blockscout", + "author": "Blockscout", + "license": "GPL-3.0", + "engines": { + "node": "16.x", + "npm": "8.x" + }, + "scripts": { + "deploy": "webpack --mode production", + "watch": "webpack --mode development --watch", + "build": "webpack --mode development", + "test": "jest", + "eslint": "eslint js/**" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.2.0", + "@tarekraafat/autocomplete.js": "^10.2.7", + "@walletconnect/web3-provider": "^1.8.0", + "assert": "^2.0.0", + "bignumber.js": "^9.1.0", + "bootstrap": "^4.6.0", + "chart.js": "^3.9.1", + "chartjs-adapter-luxon": "^1.2.0", + "clipboard": "^2.0.11", + "core-js": "^3.26.0", + "crypto-browserify": "^3.12.0", + "dropzone": "^5.9.3", + "eth-net-props": "^1.0.41", + "highlight.js": "^11.6.0", + "https-browserify": "^1.0.0", + "humps": "^2.0.1", + "jquery": "^3.6.1", + "js-cookie": "^3.0.1", + "lodash.debounce": "^4.0.8", + "lodash.differenceby": "^4.8.0", + "lodash.find": "^4.6.0", + "lodash.first": "^3.0.0", + "lodash.forin": "^4.4.0", + "lodash.get": "^4.4.2", + "lodash.intersectionby": "^4.7.0", + "lodash.isobject": "^3.0.2", + "lodash.keys": "^4.2.0", + "lodash.last": "^3.0.0", + "lodash.map": "^4.6.0", + "lodash.max": "^4.0.1", + "lodash.merge": "^4.6.2", + "lodash.min": "^4.0.1", + "lodash.noop": "^3.0.1", + "lodash.omit": "^4.5.0", + "lodash.rangeright": "^4.2.0", + "lodash.reduce": "^4.6.0", + "luxon": "^3.1.0", + "moment": "^2.29.4", + "nanomorph": "^5.4.0", + "numeral": "^2.0.6", + "os-browserify": "^0.3.0", + "path-parser": "^6.1.0", + "phoenix": "file:../../../deps/phoenix", + "phoenix_html": "file:../../../deps/phoenix_html", + "pikaday": "^1.8.2", + "popper.js": "^1.14.7", + "reduce-reducers": "^1.0.4", + "redux": "^4.2.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.1.1", + "sweetalert2": "^11.6.7", + "urijs": "^1.19.11", + "url": "^0.11.0", + "util": "^0.12.5", + "web3": "^1.8.0", + "web3modal": "^1.9.9", + "xss": "^1.0.14" + }, + "devDependencies": { + "@babel/core": "^7.20.2", + "@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": "^4.2.2", + "eslint": "^8.27.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "file-loader": "^6.2.0", + "jest": "^29.3.1", + "jest-environment-jsdom": "^29.3.0", + "mini-css-extract-plugin": "^2.6.1", + "postcss": "^8.4.18", + "postcss-loader": "^7.0.1", + "sass": "^1.56.0", + "sass-loader": "^13.1.0", + "style-loader": "^3.3.1", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0" + }, + "jest": { + "moduleNameMapper": { + "/css/app.scss": "/__mocks__/css/app.scss.js" + } + } +} diff --git a/apps/block_scout_web/assets/postcss.config.js b/apps/block_scout_web/assets/postcss.config.js new file mode 100644 index 0000000..908944f --- /dev/null +++ b/apps/block_scout_web/assets/postcss.config.js @@ -0,0 +1,9 @@ +module.exports = { + plugins: [ + require('autoprefixer')({ + overrideBrowserslist: [ + 'last 4 versions' + ] + }) + ] +}; diff --git a/apps/block_scout_web/assets/static/android-chrome-192x192.png b/apps/block_scout_web/assets/static/android-chrome-192x192.png new file mode 100644 index 0000000..22e910c Binary files /dev/null and b/apps/block_scout_web/assets/static/android-chrome-192x192.png differ diff --git a/apps/block_scout_web/assets/static/android-chrome-512x512.png b/apps/block_scout_web/assets/static/android-chrome-512x512.png new file mode 100644 index 0000000..35fad51 Binary files /dev/null and b/apps/block_scout_web/assets/static/android-chrome-512x512.png differ diff --git a/apps/block_scout_web/assets/static/apple-touch-icon.png b/apps/block_scout_web/assets/static/apple-touch-icon.png new file mode 100644 index 0000000..445c681 Binary files /dev/null and b/apps/block_scout_web/assets/static/apple-touch-icon.png differ diff --git a/apps/block_scout_web/assets/static/browserconfig.xml b/apps/block_scout_web/assets/static/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/apps/block_scout_web/assets/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/apps/block_scout_web/assets/static/images/average_time.svg b/apps/block_scout_web/assets/static/images/average_time.svg new file mode 100644 index 0000000..74318c4 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/average_time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/block.svg b/apps/block_scout_web/assets/static/images/block.svg new file mode 100644 index 0000000..88aa9eb --- /dev/null +++ b/apps/block_scout_web/assets/static/images/block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/blocks.svg b/apps/block_scout_web/assets/static/images/blocks.svg new file mode 100644 index 0000000..759aa92 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/blocks.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/blockscout_logo.svg b/apps/block_scout_web/assets/static/images/blockscout_logo.svg new file mode 100644 index 0000000..2ac7cff --- /dev/null +++ b/apps/block_scout_web/assets/static/images/blockscout_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/callisto_logo.svg b/apps/block_scout_web/assets/static/images/callisto_logo.svg new file mode 100644 index 0000000..88d5c12 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/callisto_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg b/apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg new file mode 100644 index 0000000..0d2a8d9 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/classic_logo.svg b/apps/block_scout_web/assets/static/images/classic_logo.svg new file mode 100644 index 0000000..1187479 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/classic_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/controller.svg b/apps/block_scout_web/assets/static/images/controller.svg new file mode 100644 index 0000000..081ed3b --- /dev/null +++ b/apps/block_scout_web/assets/static/images/controller.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/cube.svg b/apps/block_scout_web/assets/static/images/cube.svg new file mode 100644 index 0000000..b4bd863 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/cube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/circles/balance.svg b/apps/block_scout_web/assets/static/images/custom-themes/circles/balance.svg new file mode 100644 index 0000000..3078ec9 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/circles/balance.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/custom-themes/circles/copy-circles.svg b/apps/block_scout_web/assets/static/images/custom-themes/circles/copy-circles.svg new file mode 100644 index 0000000..2a4c168 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/circles/copy-circles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/circles/footer_logo.svg b/apps/block_scout_web/assets/static/images/custom-themes/circles/footer_logo.svg new file mode 100644 index 0000000..8b78714 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/circles/footer_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/circles/logo.svg b/apps/block_scout_web/assets/static/images/custom-themes/circles/logo.svg new file mode 100644 index 0000000..ca71ddb --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/circles/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/circles/qr-circles.svg b/apps/block_scout_web/assets/static/images/custom-themes/circles/qr-circles.svg new file mode 100644 index 0000000..eb21a05 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/circles/qr-circles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/copy-df.svg b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/copy-df.svg new file mode 100644 index 0000000..56f7551 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/copy-df.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/dark_forest_logo.svg b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/dark_forest_logo.svg new file mode 100644 index 0000000..21ffb9d --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/dark_forest_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/pic_balance.svg b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/pic_balance.svg new file mode 100644 index 0000000..cfe8f39 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/pic_balance.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/planet.svg b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/planet.svg new file mode 100644 index 0000000..0fc63f7 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/planet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/qr-df.svg b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/qr-df.svg new file mode 100644 index 0000000..fb13829 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/qr-df.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/union.svg b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/union.svg new file mode 100644 index 0000000..56a4854 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/custom-themes/dark-forest/union.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/dai_logo.svg b/apps/block_scout_web/assets/static/images/dai_logo.svg new file mode 100644 index 0000000..9c6539f --- /dev/null +++ b/apps/block_scout_web/assets/static/images/dai_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/ellaism_logo.svg b/apps/block_scout_web/assets/static/images/ellaism_logo.svg new file mode 100644 index 0000000..bb022aa --- /dev/null +++ b/apps/block_scout_web/assets/static/images/ellaism_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/errors-img/etc-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/etc-block-not-found.png new file mode 100644 index 0000000..ee65aed Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/etc-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/etc-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/etc-block-not-found@2x.png new file mode 100644 index 0000000..83e0f01 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/etc-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/etc-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/etc-page-not-found.png new file mode 100644 index 0000000..f89a5a1 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/etc-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/etc-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/etc-page-not-found@2x.png new file mode 100644 index 0000000..4b74d65 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/etc-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/etc-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/etc-tx-not-found.png new file mode 100644 index 0000000..f4874ae Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/etc-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/etc-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/etc-tx-not-found@2x.png new file mode 100644 index 0000000..b8305e5 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/etc-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/eth-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/eth-block-not-found.png new file mode 100644 index 0000000..3a2ff7f Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/eth-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/eth-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/eth-block-not-found@2x.png new file mode 100644 index 0000000..bdb96bb Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/eth-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/eth-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/eth-page-not-found.png new file mode 100644 index 0000000..d405bd5 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/eth-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/eth-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/eth-page-not-found@2x.png new file mode 100644 index 0000000..2d212a6 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/eth-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/eth-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/eth-tx-not-found.png new file mode 100644 index 0000000..89b2390 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/eth-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/eth-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/eth-tx-not-found@2x.png new file mode 100644 index 0000000..5d5384b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/eth-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-block-not-found.png new file mode 100644 index 0000000..b817b32 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-block-not-found@2x.png new file mode 100644 index 0000000..1b3b7c4 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found.png new file mode 100644 index 0000000..cbab3b1 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found@2x.png new file mode 100644 index 0000000..a28940e Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-tx-not-found.png new file mode 100644 index 0000000..5840f37 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-tx-not-found@2x.png new file mode 100644 index 0000000..d2c6678 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/koan-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/koan-block-not-found@2x.png new file mode 100644 index 0000000..33852e3 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/koan-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/kovan-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/kovan-block-not-found.png new file mode 100644 index 0000000..d75385c Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/kovan-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/kovan-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/kovan-block-not-found@2x.png new file mode 100644 index 0000000..fc774c4 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/kovan-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/kovan-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/kovan-page-not-found.png new file mode 100644 index 0000000..9611a9e Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/kovan-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/kovan-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/kovan-page-not-found@2x.png new file mode 100644 index 0000000..fde639e Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/kovan-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/kovan-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/kovan-tx-not-found.png new file mode 100644 index 0000000..0dba6fe Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/kovan-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/kovan-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/kovan-tx-not-found@2x.png new file mode 100644 index 0000000..6c6c73a Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/kovan-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/lukso-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/lukso-block-not-found.png new file mode 100644 index 0000000..76288ee Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/lukso-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/lukso-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/lukso-block-not-found@2x.png new file mode 100644 index 0000000..d5a30ce Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/lukso-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/lukso-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/lukso-page-not-found.png new file mode 100644 index 0000000..4ac9c92 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/lukso-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/lukso-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/lukso-page-not-found@2x.png new file mode 100644 index 0000000..0108e1d Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/lukso-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/lukso-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/lukso-tx-not-found.png new file mode 100644 index 0000000..b769c0a Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/lukso-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/lukso-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/lukso-tx-not-found@2x.png new file mode 100644 index 0000000..a8dcfb2 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/lukso-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/pic-404.svg b/apps/block_scout_web/assets/static/images/errors-img/pic-404.svg new file mode 100644 index 0000000..0b4e8a5 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/errors-img/pic-404.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/errors-img/poa-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/poa-block-not-found.png new file mode 100644 index 0000000..672403c Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/poa-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/poa-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/poa-block-not-found@2x.png new file mode 100644 index 0000000..c5463d0 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/poa-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/poa-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/poa-page-not-found.png new file mode 100644 index 0000000..3f018b1 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/poa-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/poa-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/poa-page-not-found@2x.png new file mode 100644 index 0000000..0f4cd1b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/poa-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/poa-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/poa-tx-not-found.png new file mode 100644 index 0000000..c68c2fb Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/poa-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/poa-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/poa-tx-not-found@2x.png new file mode 100644 index 0000000..2800948 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/poa-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinkeby-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-block-not-found.png new file mode 100644 index 0000000..66e5f19 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinkeby-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-block-not-found@2x.png new file mode 100644 index 0000000..9c5ce73 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinkeby-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-page-not-found.png new file mode 100644 index 0000000..8857aa6 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinkeby-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-page-not-found@2x.png new file mode 100644 index 0000000..8abedad Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinkeby-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-tx-not-found.png new file mode 100644 index 0000000..63194c6 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinkeby-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-tx-not-found@2x.png new file mode 100644 index 0000000..3b19876 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinkeby-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinnkeby-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/rinnkeby-block-not-found.png new file mode 100644 index 0000000..d99ca1a Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinnkeby-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rinnkeby-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/rinnkeby-block-not-found@2x.png new file mode 100644 index 0000000..c3cf19b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rinnkeby-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/ropsten-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/ropsten-block-not-found.png new file mode 100644 index 0000000..232a0e0 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/ropsten-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/ropsten-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/ropsten-block-not-found@2x.png new file mode 100644 index 0000000..9d7b2aa Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/ropsten-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/ropsten-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/ropsten-page-not-found.png new file mode 100644 index 0000000..e7549c3 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/ropsten-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/ropsten-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/ropsten-page-not-found@2x.png new file mode 100644 index 0000000..d53f54b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/ropsten-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/ropsten-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/ropsten-tx-not-found.png new file mode 100644 index 0000000..ffab5a9 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/ropsten-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/ropsten-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/ropsten-tx-not-found@2x.png new file mode 100644 index 0000000..fec3b7c Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/ropsten-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rsk-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/rsk-block-not-found.png new file mode 100644 index 0000000..a7600d6 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rsk-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rsk-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/rsk-block-not-found@2x.png new file mode 100644 index 0000000..eac366b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rsk-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rsk-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/rsk-page-not-found.png new file mode 100644 index 0000000..3bb3854 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rsk-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rsk-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/rsk-page-not-found@2x.png new file mode 100644 index 0000000..4653438 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rsk-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rsk-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/rsk-tx-not-found.png new file mode 100644 index 0000000..06efdcb Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rsk-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/rsk-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/rsk-tx-not-found@2x.png new file mode 100644 index 0000000..0831b5c Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/rsk-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/sokol-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/sokol-block-not-found.png new file mode 100644 index 0000000..a7600d6 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/sokol-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/sokol-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/sokol-block-not-found@2x.png new file mode 100644 index 0000000..eac366b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/sokol-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/sokol-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/sokol-page-not-found.png new file mode 100644 index 0000000..3bb3854 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/sokol-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/sokol-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/sokol-page-not-found@2x.png new file mode 100644 index 0000000..4653438 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/sokol-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/sokol-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/sokol-tx-not-found.png new file mode 100644 index 0000000..06efdcb Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/sokol-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/sokol-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/sokol-tx-not-found@2x.png new file mode 100644 index 0000000..0831b5c Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/sokol-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/xdai-block-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/xdai-block-not-found.png new file mode 100644 index 0000000..2d5cd4b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/xdai-block-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/xdai-block-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/xdai-block-not-found@2x.png new file mode 100644 index 0000000..7d50faf Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/xdai-block-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/xdai-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/xdai-page-not-found.png new file mode 100644 index 0000000..e96f0c2 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/xdai-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/xdai-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/xdai-page-not-found@2x.png new file mode 100644 index 0000000..47d170b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/xdai-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/xdai-tx-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/xdai-tx-not-found.png new file mode 100644 index 0000000..5840f37 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/xdai-tx-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/xdai-tx-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/xdai-tx-not-found@2x.png new file mode 100644 index 0000000..d2c6678 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/xdai-tx-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/eth.png b/apps/block_scout_web/assets/static/images/eth.png new file mode 100644 index 0000000..62e8713 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/eth.png differ diff --git a/apps/block_scout_web/assets/static/images/ether1_logo.svg b/apps/block_scout_web/assets/static/images/ether1_logo.svg new file mode 100644 index 0000000..294200a --- /dev/null +++ b/apps/block_scout_web/assets/static/images/ether1_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/ethercore_logo.svg b/apps/block_scout_web/assets/static/images/ethercore_logo.svg new file mode 100644 index 0000000..993c43d --- /dev/null +++ b/apps/block_scout_web/assets/static/images/ethercore_logo.svg @@ -0,0 +1,10139 @@ + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/ethercore_testnet_logo.svg b/apps/block_scout_web/assets/static/images/ethercore_testnet_logo.svg new file mode 100644 index 0000000..d114fb8 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/ethercore_testnet_logo.svg @@ -0,0 +1,5382 @@ + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/ethereum.png b/apps/block_scout_web/assets/static/images/ethereum.png new file mode 100644 index 0000000..23e2ba7 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/ethereum.png differ diff --git a/apps/block_scout_web/assets/static/images/ethereum_logo.svg b/apps/block_scout_web/assets/static/images/ethereum_logo.svg new file mode 100644 index 0000000..6ca5984 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/ethereum_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/expanse_logo.png b/apps/block_scout_web/assets/static/images/expanse_logo.png new file mode 100644 index 0000000..bfeaa10 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/expanse_logo.png differ diff --git a/apps/block_scout_web/assets/static/images/favicon-16x16.png b/apps/block_scout_web/assets/static/images/favicon-16x16.png new file mode 100644 index 0000000..4d11480 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/favicon-16x16.png differ diff --git a/apps/block_scout_web/assets/static/images/favicon-32x32.png b/apps/block_scout_web/assets/static/images/favicon-32x32.png new file mode 100644 index 0000000..a97445a Binary files /dev/null and b/apps/block_scout_web/assets/static/images/favicon-32x32.png differ diff --git a/apps/block_scout_web/assets/static/images/favicon.ico b/apps/block_scout_web/assets/static/images/favicon.ico new file mode 100644 index 0000000..9e95458 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/favicon.ico differ diff --git a/apps/block_scout_web/assets/static/images/gc_logo_2.svg b/apps/block_scout_web/assets/static/images/gc_logo_2.svg new file mode 100644 index 0000000..d7bbdd8 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/gc_logo_2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/block_scout_web/assets/static/images/gc_logo_old.svg b/apps/block_scout_web/assets/static/images/gc_logo_old.svg new file mode 100644 index 0000000..ffc8501 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/gc_logo_old.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/gc_logo_rebrand.svg b/apps/block_scout_web/assets/static/images/gc_logo_rebrand.svg new file mode 100644 index 0000000..8010c1c --- /dev/null +++ b/apps/block_scout_web/assets/static/images/gc_logo_rebrand.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/gochain_logo.png b/apps/block_scout_web/assets/static/images/gochain_logo.png new file mode 100644 index 0000000..d60ce4e Binary files /dev/null and b/apps/block_scout_web/assets/static/images/gochain_logo.png differ diff --git a/apps/block_scout_web/assets/static/images/goerli_logo.svg b/apps/block_scout_web/assets/static/images/goerli_logo.svg new file mode 100644 index 0000000..7e1efbf --- /dev/null +++ b/apps/block_scout_web/assets/static/images/goerli_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/blockchair.png b/apps/block_scout_web/assets/static/images/icons/blockchair.png new file mode 100644 index 0000000..f62ffa6 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/blockchair.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/blockchair@2x.png b/apps/block_scout_web/assets/static/images/icons/blockchair@2x.png new file mode 100644 index 0000000..ad11e73 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/blockchair@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/check-1.svg b/apps/block_scout_web/assets/static/images/icons/check-1.svg new file mode 100644 index 0000000..43edabe --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/check-1.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/icons/copy.svg b/apps/block_scout_web/assets/static/images/icons/copy.svg new file mode 100644 index 0000000..89c13cb --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/copy.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/dots.svg b/apps/block_scout_web/assets/static/images/icons/dots.svg new file mode 100644 index 0000000..1f7dccd --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/dots.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/icons/etherchain.png b/apps/block_scout_web/assets/static/images/icons/etherchain.png new file mode 100755 index 0000000..b864c20 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/etherchain.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/etherchain@2x.png b/apps/block_scout_web/assets/static/images/icons/etherchain@2x.png new file mode 100755 index 0000000..f3e7267 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/etherchain@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/etherscan.png b/apps/block_scout_web/assets/static/images/icons/etherscan.png new file mode 100644 index 0000000..7bf1060 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/etherscan.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/etherscan@2x.png b/apps/block_scout_web/assets/static/images/icons/etherscan@2x.png new file mode 100644 index 0000000..92e6979 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/etherscan@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/fontawesome/bar-chart.svg b/apps/block_scout_web/assets/static/images/icons/fontawesome/bar-chart.svg new file mode 100644 index 0000000..26f56f5 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/fontawesome/bar-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/fontawesome/github.svg b/apps/block_scout_web/assets/static/images/icons/fontawesome/github.svg new file mode 100644 index 0000000..9f3f18d --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/fontawesome/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/fontawesome/info-circle.svg b/apps/block_scout_web/assets/static/images/icons/fontawesome/info-circle.svg new file mode 100644 index 0000000..5ae697f --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/fontawesome/info-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/fontawesome/tag.svg b/apps/block_scout_web/assets/static/images/icons/fontawesome/tag.svg new file mode 100644 index 0000000..d18e312 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/fontawesome/tag.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/fontawesome/telegram.svg b/apps/block_scout_web/assets/static/images/icons/fontawesome/telegram.svg new file mode 100644 index 0000000..faa8d3e --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/fontawesome/telegram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/fontawesome/twitter.svg b/apps/block_scout_web/assets/static/images/icons/fontawesome/twitter.svg new file mode 100644 index 0000000..e189ab8 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/fontawesome/twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/link.svg b/apps/block_scout_web/assets/static/images/icons/link.svg new file mode 100644 index 0000000..097e437 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/icons/metamask-fox.svg b/apps/block_scout_web/assets/static/images/icons/metamask-fox.svg new file mode 100644 index 0000000..6cb41ba --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/metamask-fox.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/icons/pic-empty.svg b/apps/block_scout_web/assets/static/images/icons/pic-empty.svg new file mode 100644 index 0000000..97a4514 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/pic-empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/plus.svg b/apps/block_scout_web/assets/static/images/icons/plus.svg new file mode 100644 index 0000000..838e2a4 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/remove.svg b/apps/block_scout_web/assets/static/images/icons/remove.svg new file mode 100644 index 0000000..8298bf9 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/remove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/swap/1inch.svg b/apps/block_scout_web/assets/static/images/icons/swap/1inch.svg new file mode 100644 index 0000000..88cd538 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/swap/1inch.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/icons/swap/component.png b/apps/block_scout_web/assets/static/images/icons/swap/component.png new file mode 100644 index 0000000..50a1ed5 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/swap/component.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/swap/cowswap.png b/apps/block_scout_web/assets/static/images/icons/swap/cowswap.png new file mode 100644 index 0000000..8612561 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/swap/cowswap.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/swap/curve.svg b/apps/block_scout_web/assets/static/images/icons/swap/curve.svg new file mode 100644 index 0000000..71fbd20 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/swap/curve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/swap/honeyswap.png b/apps/block_scout_web/assets/static/images/icons/swap/honeyswap.png new file mode 100644 index 0000000..723593b Binary files /dev/null and b/apps/block_scout_web/assets/static/images/icons/swap/honeyswap.png differ diff --git a/apps/block_scout_web/assets/static/images/icons/swap/sushi.svg b/apps/block_scout_web/assets/static/images/icons/swap/sushi.svg new file mode 100644 index 0000000..8d1ebf4 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/swap/sushi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/swap/swapr.svg b/apps/block_scout_web/assets/static/images/icons/swap/swapr.svg new file mode 100644 index 0000000..fc63da2 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/swap/swapr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/icons/withdraw.svg b/apps/block_scout_web/assets/static/images/icons/withdraw.svg new file mode 100644 index 0000000..575e7a5 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/icons/withdraw.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/kaly_footer.png b/apps/block_scout_web/assets/static/images/kaly_footer.png new file mode 100644 index 0000000..a97445a Binary files /dev/null and b/apps/block_scout_web/assets/static/images/kaly_footer.png differ diff --git a/apps/block_scout_web/assets/static/images/kalycoin-logo.png b/apps/block_scout_web/assets/static/images/kalycoin-logo.png new file mode 100644 index 0000000..8591385 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/kalycoin-logo.png differ diff --git a/apps/block_scout_web/assets/static/images/kovan_logo.svg b/apps/block_scout_web/assets/static/images/kovan_logo.svg new file mode 100644 index 0000000..d57a709 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/kovan_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/last_block.svg b/apps/block_scout_web/assets/static/images/last_block.svg new file mode 100644 index 0000000..28c3f04 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/last_block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/logo.svg b/apps/block_scout_web/assets/static/images/logo.svg new file mode 100644 index 0000000..a8df6bf --- /dev/null +++ b/apps/block_scout_web/assets/static/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/lukso_dashboard_image.png b/apps/block_scout_web/assets/static/images/lukso_dashboard_image.png new file mode 100755 index 0000000..9487461 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/lukso_dashboard_image.png differ diff --git a/apps/block_scout_web/assets/static/images/lukso_logo.png b/apps/block_scout_web/assets/static/images/lukso_logo.png new file mode 100755 index 0000000..266804f Binary files /dev/null and b/apps/block_scout_web/assets/static/images/lukso_logo.png differ diff --git a/apps/block_scout_web/assets/static/images/lukso_logo_footer.png b/apps/block_scout_web/assets/static/images/lukso_logo_footer.png new file mode 100755 index 0000000..488fc91 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/lukso_logo_footer.png differ diff --git a/apps/block_scout_web/assets/static/images/musicoin_logo.svg b/apps/block_scout_web/assets/static/images/musicoin_logo.svg new file mode 100644 index 0000000..c6d8d41 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/musicoin_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.svg new file mode 100644 index 0000000..bc6220a --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/circle-xusdt.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/circle-xusdt.svg new file mode 100644 index 0000000..ba01ae4 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/circle-xusdt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.svg new file mode 100644 index 0000000..852943c --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-mainnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-mainnet.svg new file mode 100644 index 0000000..7de7571 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-mainnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.svg new file mode 100644 index 0000000..e2293d0 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/kovan-testnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/kovan-testnet.svg new file mode 100644 index 0000000..2baa304 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/kovan-testnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/lukso-l14-testnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/lukso-l14-testnet.svg new file mode 100644 index 0000000..f2b3017 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/lukso-l14-testnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.svg new file mode 100644 index 0000000..826aecc --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/poa-sokol.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/poa-sokol.svg new file mode 100644 index 0000000..70b0d1e --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/poa-sokol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby-testnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby-testnet.svg new file mode 100644 index 0000000..e13556c --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby-testnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.svg new file mode 100644 index 0000000..5f44f21 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.svg new file mode 100644 index 0000000..b22027a --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.svg new file mode 100644 index 0000000..3667ade --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/pirl_logo.svg b/apps/block_scout_web/assets/static/images/pirl_logo.svg new file mode 100644 index 0000000..d769dc4 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/pirl_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/poa_logo.svg b/apps/block_scout_web/assets/static/images/poa_logo.svg new file mode 100644 index 0000000..3210331 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/poa_logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/purple-block.svg b/apps/block_scout_web/assets/static/images/purple-block.svg new file mode 100644 index 0000000..c198cbe --- /dev/null +++ b/apps/block_scout_web/assets/static/images/purple-block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/rinkeby_logo.svg b/apps/block_scout_web/assets/static/images/rinkeby_logo.svg new file mode 100644 index 0000000..4659ef9 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/rinkeby_logo.svg @@ -0,0 +1 @@ + diff --git a/apps/block_scout_web/assets/static/images/ropsten_logo.svg b/apps/block_scout_web/assets/static/images/ropsten_logo.svg new file mode 100644 index 0000000..3f1e7e5 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/ropsten_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/rsk_logo.svg b/apps/block_scout_web/assets/static/images/rsk_logo.svg new file mode 100644 index 0000000..3f469de --- /dev/null +++ b/apps/block_scout_web/assets/static/images/rsk_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/smart_contract.svg b/apps/block_scout_web/assets/static/images/smart_contract.svg new file mode 100644 index 0000000..cd9b5c1 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/smart_contract.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/social_logo.svg b/apps/block_scout_web/assets/static/images/social_logo.svg new file mode 100644 index 0000000..aab45fa --- /dev/null +++ b/apps/block_scout_web/assets/static/images/social_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/sokol_logo.svg b/apps/block_scout_web/assets/static/images/sokol_logo.svg new file mode 100644 index 0000000..3017562 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/sokol_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/spinner.svg b/apps/block_scout_web/assets/static/images/spinner.svg new file mode 100644 index 0000000..dd29cb7 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/spinner.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/tobalaba_logo.svg b/apps/block_scout_web/assets/static/images/tobalaba_logo.svg new file mode 100644 index 0000000..d26774f --- /dev/null +++ b/apps/block_scout_web/assets/static/images/tobalaba_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/token.svg b/apps/block_scout_web/assets/static/images/token.svg new file mode 100644 index 0000000..8390f97 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/token.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/tomochain_logo.svg b/apps/block_scout_web/assets/static/images/tomochain_logo.svg new file mode 100644 index 0000000..71cfd2b --- /dev/null +++ b/apps/block_scout_web/assets/static/images/tomochain_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/transaction.svg b/apps/block_scout_web/assets/static/images/transaction.svg new file mode 100644 index 0000000..824b4cd --- /dev/null +++ b/apps/block_scout_web/assets/static/images/transaction.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/transactions.svg b/apps/block_scout_web/assets/static/images/transactions.svg new file mode 100644 index 0000000..7fad652 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/transactions.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/assets/static/images/wanchain_logo.png b/apps/block_scout_web/assets/static/images/wanchain_logo.png new file mode 100644 index 0000000..788cccc Binary files /dev/null and b/apps/block_scout_web/assets/static/images/wanchain_logo.png differ diff --git a/apps/block_scout_web/assets/static/images/xdai_alternative.svg b/apps/block_scout_web/assets/static/images/xdai_alternative.svg new file mode 100644 index 0000000..ac9749d --- /dev/null +++ b/apps/block_scout_web/assets/static/images/xdai_alternative.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/xdai_logo.svg b/apps/block_scout_web/assets/static/images/xdai_logo.svg new file mode 100644 index 0000000..3026b95 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/xdai_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/xusdt-logo-footer.svg b/apps/block_scout_web/assets/static/images/xusdt-logo-footer.svg new file mode 100644 index 0000000..67bae45 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/xusdt-logo-footer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/xusdt-logo-top.svg b/apps/block_scout_web/assets/static/images/xusdt-logo-top.svg new file mode 100644 index 0000000..c52ec4e --- /dev/null +++ b/apps/block_scout_web/assets/static/images/xusdt-logo-top.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/manifest.webmanifest b/apps/block_scout_web/assets/static/manifest.webmanifest new file mode 100644 index 0000000..b20abb7 --- /dev/null +++ b/apps/block_scout_web/assets/static/manifest.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/apps/block_scout_web/assets/static/mstile-150x150.png b/apps/block_scout_web/assets/static/mstile-150x150.png new file mode 100644 index 0000000..07bb1b3 Binary files /dev/null and b/apps/block_scout_web/assets/static/mstile-150x150.png differ diff --git a/apps/block_scout_web/assets/static/robots.txt b/apps/block_scout_web/assets/static/robots.txt new file mode 100644 index 0000000..3c9c7c0 --- /dev/null +++ b/apps/block_scout_web/assets/static/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/apps/block_scout_web/assets/static/safari-pinned-tab.svg b/apps/block_scout_web/assets/static/safari-pinned-tab.svg new file mode 100644 index 0000000..0c9f9bd --- /dev/null +++ b/apps/block_scout_web/assets/static/safari-pinned-tab.svg @@ -0,0 +1,39 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js new file mode 100644 index 0000000..0a1ec27 --- /dev/null +++ b/apps/block_scout_web/assets/webpack.config.js @@ -0,0 +1,173 @@ +const path = require('path') +const TerserJSPlugin = require('terser-webpack-plugin') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const { ContextReplacementPlugin } = require('webpack') +const glob = require('glob') +const webpack = require('webpack') + +function transpileViewScript(file) { + return { + entry: file, + output: { + filename: file.replace('./js/view_specific/', ''), + path: path.resolve(__dirname, '../priv/static/js') + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader' + } + } + ] + } + } +}; + +const jsOptimizationParams = { + parallel: true +} + +const appJs = + { + entry: { + 'app': './js/app.js', + 'chart-loader': './js/chart-loader.js', + 'balance-chart-loader': './js/balance-chart-loader.js', + 'chain': './js/pages/chain.js', + 'blocks': './js/pages/blocks.js', + 'address': './js/pages/address.js', + 'address-transactions': './js/pages/address/transactions.js', + 'address-token-transfers': './js/pages/address/token_transfers.js', + 'address-coin-balances': './js/pages/address/coin_balances.js', + 'address-internal-transactions': './js/pages/address/internal_transactions.js', + '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', + 'token-counters': './js/pages/token_counters.js', + 'token-transfers': './js/pages/token/token_transfers.js', + 'admin-tasks': './js/pages/admin/tasks.js', + 'token-contract': './js/pages/token_contract.js', + 'smart-contract-helpers': './js/lib/smart_contract/index.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', + 'async-listing-load': './js/lib/async_listing_load', + 'non-critical': './css/non-critical.scss', + 'main-page': './css/main-page.scss', + 'tokens': './js/pages/token/search.js', + 'text-ad': './js/lib/text_ad.js', + 'banner': './js/lib/banner.js', + 'autocomplete': './js/lib/autocomplete.js', + 'search-results': './js/pages/search-results/search.js', + 'token-overview': './js/pages/token/overview.js', + 'export-csv': './css/export-csv.scss', + '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', + path: path.resolve(__dirname, '../priv/static/js') + }, + optimization: { + minimizer: [new TerserJSPlugin(jsOptimizationParams), new CssMinimizerPlugin()], + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader' + } + }, + { + test: /\.scss$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + esModule: false, + }, + }, { + loader: 'css-loader' + }, { + loader: 'postcss-loader' + }, { + loader: 'sass-loader', + options: { + sassOptions: { + precision: 8, + includePaths: [ + 'node_modules/bootstrap/scss', + 'node_modules/@fortawesome/fontawesome-free/scss' + ] + } + } + } + ] + }, { + test: /\.(svg|ttf|eot|woff|woff2)$/, + use: { + loader: 'file-loader', + options: { + name: '[name].[ext]', + outputPath: '../fonts/', + publicPath: '../fonts/' + } + } + }, { + test: /\.(png)$/, + use: { + loader: 'file-loader' + } + } + ] + }, + resolve: { + fallback: { + "os": require.resolve("os-browserify/browser"), + "https": require.resolve("https-browserify"), + "http": require.resolve("stream-http"), + "crypto": require.resolve("crypto-browserify"), + "util": require.resolve("util/"), + "stream": require.resolve("stream-browserify"), + "assert": require.resolve("assert/"), + } + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: '../css/[name].css' + }), + new CopyWebpackPlugin( + { + patterns: [ + { from: 'static/', to: '../' } + ] + } + ), + 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) + }), + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'], + }), + ] + } + +const viewScripts = glob.sync('./js/view_specific/**/*.js').map(transpileViewScript) + +module.exports = viewScripts.concat(appJs) diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs new file mode 100644 index 0000000..c3dd7b0 --- /dev/null +++ b/apps/block_scout_web/config/config.exs @@ -0,0 +1,115 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. +import Config + +network_path = + "NETWORK_PATH" + |> System.get_env("/") + |> (&(if !String.ends_with?(&1, "/") do + &1 <> "/" + else + &1 + end)).() + +api_path = + "API_PATH" + |> System.get_env("/") + |> (&(if !String.ends_with?(&1, "/") do + &1 <> "/" + else + &1 + end)).() + +# General application configuration +config :block_scout_web, + namespace: BlockScoutWeb, + ecto_repos: [Explorer.Repo, Explorer.Repo.Account] + +config :block_scout_web, + admin_panel_enabled: System.get_env("ADMIN_PANEL_ENABLED", "") == "true" + +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: [ + path: network_path, + api_path: api_path + ], + render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)], + pubsub_server: BlockScoutWeb.PubSub + +config :block_scout_web, BlockScoutWeb.Tracer, + service: :block_scout_web, + adapter: SpandexDatadog.Adapter, + trace_key: :blockscout + +# Configures gettext +config :block_scout_web, BlockScoutWeb.Gettext, locales: ~w(en), default_locale: "en" + +config :block_scout_web, BlockScoutWeb.SocialMedia, + twitter: "PoaNetwork", + telegram: "poa_network", + facebook: "PoaNetwork", + instagram: "PoaNetwork" + +config :block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, + # days + history_size: 30 + +config :ex_cldr, + default_locale: "en", + default_backend: BlockScoutWeb.Cldr + +config :logger, :block_scout_web, + # keep synced with `config/config.exs` + format: "$dateT$time $metadata[$level] $message\n", + metadata: + ~w(application fetcher request_id first_block_number last_block_number missing_block_range_count missing_block_count + block_number step count error_count shrunk import_id transaction_id)a, + metadata_filter: [application: :block_scout_web] + +config :prometheus, BlockScoutWeb.Prometheus.Instrumenter, + # override default for Phoenix 1.4 compatibility + # * `:transport_name` to `:transport` + # * remove `:vsn` + channel_join_labels: [:channel, :topic, :transport], + # override default for Phoenix 1.4 compatibility + # * `:transport_name` to `:transport` + # * remove `:vsn` + channel_receive_labels: [:channel, :topic, :transport, :event] + +config :spandex_phoenix, tracer: BlockScoutWeb.Tracer + +config :wobserver, + # return only the local node + discovery: :none, + mode: :plug + +config :block_scout_web, BlockScoutWeb.ApiRouter, + writing_enabled: System.get_env("DISABLE_WRITE_API") != "true", + reading_enabled: System.get_env("DISABLE_READ_API") != "true", + wobserver_enabled: System.get_env("WOBSERVER_ENABLED") == "true" + +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"] + } + ] + +config :hammer, + backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]} + +# 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/block_scout_web/config/dev.exs b/apps/block_scout_web/config/dev.exs new file mode 100644 index 0000000..d26e1b2 --- /dev/null +++ b/apps/block_scout_web/config/dev.exs @@ -0,0 +1,66 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with webpack to recompile .js and .css sources. + +config :block_scout_web, BlockScoutWeb.Endpoint, + debug_errors: true, + code_reloader: true, + check_origin: false, + watchers: [ + node: [ + "node_modules/webpack/bin/webpack.js", + "--mode", + "development", + "--watch", + cd: Path.expand("../assets", __DIR__) + ] + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# command from your terminal: +# +# openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem +# +# The `http:` config above can be replaced with: +# +# https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :block_scout_web, BlockScoutWeb.Endpoint, + live_reload: [ + patterns: [ + ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, + ~r{priv/gettext/.*(po)$}, + ~r{lib/block_scout_web/views/.*(ex)$}, + ~r{lib/block_scout_web/templates/.*(eex)$} + ] + ] + +config :block_scout_web, BlockScoutWeb.Tracer, env: "dev", disabled?: true + +config :logger, :block_scout_web, + level: :debug, + path: Path.absname("logs/dev/block_scout_web.log") + +config :logger, :api, + level: :debug, + path: Path.absname("logs/dev/api.log"), + metadata_filter: [fetcher: :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 new file mode 100644 index 0000000..366b886 --- /dev/null +++ b/apps/block_scout_web/config/prod.exs @@ -0,0 +1,33 @@ +import Config + +# For production, we often load configuration from external +# sources, such as your system environment. For this reason, +# you won't find the :http configuration below, but set inside +# BlockScoutWeb.Endpoint.init/2 when load_from_system_env is +# true. Any dynamic configuration should be done there. +# +# Don't forget to configure the url host to something meaningful, +# Phoenix uses this information when generating URLs. +# +# Finally, we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the mix phx.digest task +# 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 + +config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true + +config :logger, :block_scout_web, + level: :info, + path: Path.absname("logs/prod/block_scout_web.log"), + rotate: %{max_bytes: 52_428_800, keep: 19} + +config :logger, :api, + level: :debug, + 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 0000000..f465b51 --- /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 new file mode 100644 index 0000000..9c3414f --- /dev/null +++ b/apps/block_scout_web/config/test.exs @@ -0,0 +1,35 @@ +import Config + +config :block_scout_web, :sql_sandbox, true + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :block_scout_web, BlockScoutWeb.Endpoint, + http: [port: 4002], + secret_key_base: "27Swe6KtEtmN37WyEYRjKWyxYULNtrxlkCEKur4qoV+Lwtk8lafsR16ifz1XBBYj", + server: true, + pubsub_server: BlockScoutWeb.PubSub, + checksum_address_hashes: true + +config :block_scout_web, BlockScoutWeb.Tracer, disabled?: false + +config :logger, :block_scout_web, + level: :warn, + path: Path.absname("logs/test/block_scout_web.log") + +# Configure wallaby +config :wallaby, screenshot_on_failure: true, driver: Wallaby.Chrome, js_errors: false + +config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: false + +config :block_scout_web, BlockScoutWeb.Counters.InternalTransactionsIndexedCounter, 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.ex b/apps/block_scout_web/lib/block_scout_web.ex new file mode 100644 index 0000000..fddfe7c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web.ex @@ -0,0 +1,86 @@ +defmodule BlockScoutWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use BlockScoutWeb, :controller + use BlockScoutWeb, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + def version(), do: Application.get_env(:block_scout_web, :version) + + def controller do + quote do + use Phoenix.Controller, namespace: BlockScoutWeb + + import BlockScoutWeb.Controller + import BlockScoutWeb.Router.Helpers + import BlockScoutWeb.WebRouter.Helpers, except: [static_path: 2] + import BlockScoutWeb.Gettext + import BlockScoutWeb.ErrorHelpers + import Plug.Conn + + alias BlockScoutWeb.AdminRouter.Helpers, as: AdminRoutes + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/block_scout_web/templates", + namespace: BlockScoutWeb + + # Import convenience functions from controllers + import Phoenix.Controller, only: [get_flash: 2, view_module: 1] + + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + import BlockScoutWeb.{ + CurrencyHelpers, + ErrorHelpers, + Gettext, + Router.Helpers, + TabHelpers, + Tokens.Helpers, + Views.ScriptHelpers, + WeiHelpers + } + + import BlockScoutWeb.WebRouter.Helpers, except: [static_path: 2] + end + end + + def router do + quote do + use Phoenix.Router + + import Plug.Conn + import Phoenix.Controller + end + end + + def channel do + quote do + use Phoenix.Channel + + import BlockScoutWeb.Gettext + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/admin_router.ex b/apps/block_scout_web/lib/block_scout_web/admin_router.ex new file mode 100644 index 0000000..7a1c328 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/admin_router.ex @@ -0,0 +1,51 @@ +defmodule BlockScoutWeb.AdminRouter do + @moduledoc """ + Router for admin pages. + """ + + use BlockScoutWeb, :router + + alias BlockScoutWeb.Plug.FetchUserFromSession + alias BlockScoutWeb.Plug.Admin.{CheckOwnerRegistered, RequireAdminRole} + + pipeline :browser do + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(BlockScoutWeb.CSPHeader) + end + + pipeline :check_configured do + plug(CheckOwnerRegistered) + end + + pipeline :ensure_admin do + plug(FetchUserFromSession) + plug(RequireAdminRole) + end + + scope "/setup", BlockScoutWeb.Admin do + pipe_through([:browser]) + + get("/", SetupController, :configure) + post("/", SetupController, :configure_admin) + end + + scope "/login", BlockScoutWeb.Admin do + pipe_through([:browser, :check_configured]) + + get("/", SessionController, :new) + post("/", SessionController, :create) + end + + scope "/", BlockScoutWeb.Admin do + pipe_through([:browser, :check_configured, :ensure_admin]) + + get("/", DashboardController, :index) + + scope "/tasks" do + get("/create_contract_methods", TaskController, :create_contract_methods, as: :create_contract_methods) + end + end +end 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 new file mode 100644 index 0000000..d850b61 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -0,0 +1,199 @@ +defmodule RPCTranslatorForwarder do + @moduledoc """ + Phoenix router limits forwarding, + so this module is to forward old paths for backward compatibility + """ + alias BlockScoutWeb.API.RPC.RPCTranslator + defdelegate init(opts), to: RPCTranslator + defdelegate call(conn, opts), to: RPCTranslator +end + +defmodule BlockScoutWeb.ApiRouter do + @moduledoc """ + 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/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 "/main-page" do + get("/blocks", V2.MainPageController, :blocks) + get("/transactions", V2.MainPageController, :transactions) + 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.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do + get("/supply", V1.SupplyController, :supply) + post("/eth-rpc", EthRPC.EthController, :eth_request) + end + + 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.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do + forward("/", RPC.RPCTranslator, %{ + "block" => {RPC.BlockController, []}, + "account" => {RPC.AddressController, []}, + "logs" => {RPC.LogsController, []}, + "token" => {RPC.TokenController, []}, + "stats" => {RPC.StatsController, []}, + "contract" => {RPC.ContractController, [:verify]}, + "transaction" => {RPC.TransactionController, []} + }) + end + end + + # For backward compatibility. Should be removed + scope "/" do + pipe_through(:api) + alias BlockScoutWeb.API.{EthRPC, RPC} + + if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do + post("/eth-rpc", EthRPC.EthController, :eth_request) + + forward("/", RPCTranslatorForwarder, %{ + "block" => {RPC.BlockController, []}, + "account" => {RPC.AddressController, []}, + "logs" => {RPC.LogsController, []}, + "token" => {RPC.TokenController, []}, + "stats" => {RPC.StatsController, []}, + "contract" => {RPC.ContractController, [:verify]}, + "transaction" => {RPC.TransactionController, []} + }) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/application.ex b/apps/block_scout_web/lib/block_scout_web/application.ex new file mode 100644 index 0000000..2d84cc8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/application.ex @@ -0,0 +1,52 @@ +defmodule BlockScoutWeb.Application do + @moduledoc """ + Supervises `BlockScoutWeb.Endpoint` in order to serve Web UI. + """ + + use Application + + alias BlockScoutWeb.API.APILogger + alias BlockScoutWeb.Counters.{BlocksIndexedCounter, InternalTransactionsIndexedCounter} + alias BlockScoutWeb.{Endpoint, Prometheus} + alias BlockScoutWeb.RealtimeEventHandler + + def start(_type, _args) do + import Supervisor + + 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 + {Phoenix.PubSub, name: BlockScoutWeb.PubSub}, + child_spec(Endpoint, []), + {Absinthe.Subscription, Endpoint}, + {RealtimeEventHandler, name: RealtimeEventHandler}, + {BlocksIndexedCounter, name: BlocksIndexedCounter}, + {InternalTransactionsIndexedCounter, name: InternalTransactionsIndexedCounter} + ] + + opts = [strategy: :one_for_one, name: BlockScoutWeb.Supervisor, max_restarts: 1_000] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + def config_change(changed, _new, removed) do + Endpoint.config_change(changed, removed) + :ok + end +end 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 0000000..abeb3d1 --- /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 new file mode 100644 index 0000000..3d710fe --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -0,0 +1,413 @@ +defmodule BlockScoutWeb.Chain do + @moduledoc """ + Converts the `param` to the corresponding resource that uses that format of param. + """ + + import Explorer.Chain, + only: [ + find_or_insert_address_from_hash: 1, + hash_to_block: 1, + hash_to_transaction: 1, + number_to_block: 1, + string_to_address_hash: 1, + string_to_block_hash: 1, + string_to_transaction_hash: 1, + token_contract_address_from_token_name: 1 + ] + + alias Explorer.Chain.Block.Reward + + alias Explorer.Chain.{ + Address, + Address.CoinBalance, + Address.CurrentTokenBalance, + Block, + InternalTransaction, + Log, + SmartContract, + Token, + TokenTransfer, + Transaction, + Wei + } + + alias Explorer.PagingOptions + + defimpl Poison.Encoder, for: Decimal do + def encode(value, _opts) do + # silence the xref warning + decimal = Decimal + + [?\", decimal.to_string(value), ?\"] + end + end + + @page_size 50 + @default_paging_options %PagingOptions{page_size: @page_size + 1} + @address_hash_len 40 + @tx_block_hash_len 64 + + def default_paging_options do + @default_paging_options + end + + def current_filter(%{paging_options: paging_options} = params) do + params + |> Map.get("filter") + |> case do + "to" -> [direction: :to, paging_options: paging_options] + "from" -> [direction: :from, paging_options: paging_options] + _ -> [paging_options: paging_options] + end + end + + def current_filter(params) do + params + |> Map.get("filter") + |> case do + "to" -> [direction: :to] + "from" -> [direction: :from] + _ -> [] + end + end + + @spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found} + def from_param(param) + + def from_param("0x" <> number_string = param) when byte_size(number_string) == @address_hash_len, + do: address_from_param(param) + + def from_param("0x" <> number_string = param) when byte_size(number_string) == @tx_block_hash_len, + do: block_or_transaction_from_param(param) + + def from_param(param) when byte_size(param) == @address_hash_len, + do: address_from_param("0x" <> param) + + def from_param(param) when byte_size(param) == @tx_block_hash_len, + do: block_or_transaction_from_param("0x" <> param) + + def from_param(string) when is_binary(string) do + case param_to_block_number(string) do + {:ok, number} -> number_to_block(number) + _ -> token_address_from_name(string) + end + end + + def next_page_params([], _list, _params), do: nil + + def next_page_params(_, list, params) do + next_page_params = Map.merge(params, paging_params(List.last(list))) + current_items_count_str = Map.get(next_page_params, "items_count") + + items_count = + if current_items_count_str do + {current_items_count, _} = Integer.parse(current_items_count_str) + current_items_count + Enum.count(list) + else + Enum.count(list) + end + + Map.put(next_page_params, "items_count", items_count) + end + + def paging_options(%{"hash" => hash, "fetched_coin_balance" => fetched_coin_balance}) do + with {coin_balance, ""} <- Integer.parse(fetched_coin_balance), + {:ok, address_hash} <- string_to_address_hash(hash) do + [paging_options: %{@default_paging_options | key: {%Wei{value: Decimal.new(coin_balance)}, address_hash}}] + else + _ -> + [paging_options: @default_paging_options] + end + end + + def paging_options(%{ + "address_hash" => address_hash, + "tx_hash" => tx_hash, + "block_hash" => block_hash, + "holder_count" => holder_count, + "name" => name, + "inserted_at" => inserted_at, + "item_type" => item_type + }) do + [ + paging_options: %{ + @default_paging_options + | key: {address_hash, tx_hash, block_hash, holder_count, name, inserted_at, item_type} + } + ] + end + + def paging_options(%{"holder_count" => holder_count, "name" => token_name}) do + case Integer.parse(holder_count) do + {holder_count, ""} -> + [paging_options: %{@default_paging_options | key: {holder_count, token_name}}] + + _ -> + [paging_options: @default_paging_options] + end + end + + def paging_options(%{ + "block_number" => block_number_string, + "transaction_index" => transaction_index_string, + "index" => index_string + }) do + with {block_number, ""} <- Integer.parse(block_number_string), + {transaction_index, ""} <- Integer.parse(transaction_index_string), + {index, ""} <- Integer.parse(index_string) do + [paging_options: %{@default_paging_options | key: {block_number, transaction_index, index}}] + else + _ -> + [paging_options: @default_paging_options] + end + end + + def paging_options(%{"block_number" => block_number_string, "index" => index_string}) 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(%{"block_number" => block_number_string}) do + case Integer.parse(block_number_string) do + {block_number, ""} -> + [paging_options: %{@default_paging_options | key: {block_number}}] + + _ -> + [paging_options: @default_paging_options] + end + end + + def paging_options(%{"index" => index_string}) when is_binary(index_string) do + case Integer.parse(index_string) do + {index, ""} -> + [paging_options: %{@default_paging_options | key: {index}}] + + _ -> + [paging_options: @default_paging_options] + end + end + + def paging_options(%{"index" => index}) when is_integer(index) do + [paging_options: %{@default_paging_options | key: {index}}] + end + + def paging_options(%{"inserted_at" => inserted_at_string, "hash" => hash_string}) 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(%{"token_name" => name, "token_type" => type, "token_inserted_at" => inserted_at}), + do: [paging_options: %{@default_paging_options | key: {name, type, inserted_at}}] + + def paging_options(%{"value" => value, "address_hash" => address_hash}) do + [paging_options: %{@default_paging_options | key: {value, address_hash}}] + end + + def paging_options(%{"token_name" => name, "token_type" => type, "value" => value}) do + [paging_options: %{@default_paging_options | key: {name, type, value}}] + end + + def paging_options(%{"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 + [paging_options: Map.put(paging_options, key, value)] + end + + def fetch_page_number(%{"page_number" => page_number_string}) do + case Integer.parse(page_number_string) do + {number, ""} -> + number + + _ -> + 1 + 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 + %PagingOptions{options | page_number: new_page_number, page_size: new_page_size} + end + + def param_to_block_number(formatted_number) when is_binary(formatted_number) do + case Integer.parse(formatted_number) do + {number, ""} -> {:ok, number} + _ -> {:error, :invalid} + end + end + + def param_to_block_timestamp(timestamp_string) when is_binary(timestamp_string) do + case Integer.parse(timestamp_string) do + {temstamp_int, ""} -> + timestamp = + temstamp_int + |> DateTime.from_unix!(:second) + + {:ok, timestamp} + + _ -> + {:error, :invalid_timestamp} + end + end + + def param_to_block_closest(closest) when is_binary(closest) do + case closest do + "before" -> {:ok, :before} + "after" -> {:ok, :after} + _ -> {:error, :invalid_closest} + end + end + + def split_list_by_page(list_plus_one), do: Enum.split(list_plus_one, @page_size) + + defp address_from_param(param) do + case string_to_address_hash(param) do + {:ok, hash} -> + find_or_insert_address_from_hash(hash) + + :error -> + {:error, :not_found} + end + end + + defp token_address_from_name(name) do + case token_contract_address_from_token_name(name) do + {:ok, hash} -> find_or_insert_address_from_hash(hash) + _ -> {:error, :not_found} + end + end + + defp paging_params({%Address{hash: hash, fetched_coin_balance: fetched_coin_balance}, _}) do + %{"hash" => hash, "fetched_coin_balance" => Decimal.to_string(fetched_coin_balance.value)} + end + + defp paging_params(%Token{holder_count: holder_count, name: token_name}) do + %{"holder_count" => holder_count, "name" => token_name} + end + + defp paging_params([%Token{holder_count: holder_count, name: token_name}, _]) do + %{"holder_count" => holder_count, "name" => token_name} + end + + defp paging_params({%Reward{block: %{number: number}}, _}) do + %{"block_number" => number, "index" => 0} + end + + defp paging_params(%Block{number: number}) do + %{"block_number" => number} + end + + defp paging_params(%InternalTransaction{index: index, transaction_hash: transaction_hash}) do + {:ok, %Transaction{block_number: block_number, index: transaction_index}} = hash_to_transaction(transaction_hash) + %{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index} + end + + defp paging_params(%Log{index: index} = log) do + if Ecto.assoc_loaded?(log.transaction) do + %{"block_number" => log.transaction.block_number, "transaction_index" => log.transaction.index, "index" => index} + else + %{"index" => index} + end + end + + defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do + %{"inserted_at" => DateTime.to_iso8601(inserted_at), "hash" => hash} + end + + defp paging_params(%Transaction{block_number: block_number, index: index}) do + %{"block_number" => block_number, "index" => index} + end + + defp paging_params(%TokenTransfer{block_number: block_number, log_index: index}) do + %{"block_number" => block_number, "index" => index} + end + + defp paging_params(%Address.Token{name: name, type: type, inserted_at: inserted_at}) do + inserted_at_datetime = DateTime.to_iso8601(inserted_at) + + %{"token_name" => name, "token_type" => type, "token_inserted_at" => inserted_at_datetime} + end + + defp paging_params(%CurrentTokenBalance{address_hash: address_hash, value: value}) do + %{"address_hash" => to_string(address_hash), "value" => Decimal.to_integer(value)} + end + + defp paging_params({%CurrentTokenBalance{value: value}, %Token{name: name, type: type}}) do + %{"token_name" => name, "token_type" => type, "value" => Decimal.to_integer(value)} + end + + defp paging_params(%CoinBalance{block_number: block_number}) do + %{"block_number" => block_number} + end + + 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, + block_hash: block_hash, + holder_count: holder_count, + name: name, + inserted_at: inserted_at, + type: type + }) do + inserted_at_datetime = DateTime.to_iso8601(inserted_at) + + %{ + "address_hash" => address_hash, + "tx_hash" => tx_hash, + "block_hash" => block_hash, + "holder_count" => holder_count, + "name" => name, + "inserted_at" => inserted_at_datetime, + "item_type" => type + } + end + + defp block_or_transaction_from_param(param) do + with {:error, :not_found} <- transaction_from_param(param) do + hash_string_to_block(param) + end + end + + defp transaction_from_param(param) do + case string_to_transaction_hash(param) do + {:ok, hash} -> + hash_to_transaction(hash) + + :error -> + {:error, :not_found} + end + end + + defp hash_string_to_block(hash_string) do + case string_to_block_hash(hash_string) do + {:ok, hash} -> + hash_to_block(hash) + + :error -> + {:error, :not_found} + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex new file mode 100644 index 0000000..e73a54d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -0,0 +1,335 @@ +defmodule BlockScoutWeb.AddressChannel do + @moduledoc """ + Establishes pub/sub channel for address page live updates. + """ + use BlockScoutWeb, :channel + + alias BlockScoutWeb.API.V2.AddressView, as: AddressViewAPI + + alias BlockScoutWeb.{ + AddressCoinBalanceView, + AddressView, + InternalTransactionView, + TransactionView + } + + alias Explorer.{Chain, Market, Repo} + alias Explorer.Chain.{Hash, Transaction, Wei} + alias Explorer.Chain.Hash.Address, as: AddressHash + alias Explorer.ExchangeRates.Token + alias Phoenix.View + + intercept([ + "balance_update", + "coin_balance", + "count", + "internal_transaction", + "transaction", + "verification_result", + "token_transfer", + "pending_transaction" + ]) + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def join("addresses:" <> address_hash, _params, socket) do + {:ok, %{}, assign(socket, :address_hash, address_hash)} + end + + def handle_in("get_balance", _, socket) do + with {:ok, casted_address_hash} <- AddressHash.cast(socket.assigns.address_hash), + {:ok, address = %{fetched_coin_balance: balance}} when not is_nil(balance) <- + Chain.hash_to_address(casted_address_hash), + exchange_rate <- Market.get_exchange_rate(Explorer.coin()) || Token.null(), + {:ok, rendered} <- render_balance_card(address, exchange_rate, socket) do + reply = + {:ok, + %{ + balance_card: rendered, + balance: address.fetched_coin_balance.value, + fetched_coin_balance_block_number: address.fetched_coin_balance_block_number + }} + + {:reply, reply, socket} + else + _ -> + {:noreply, socket} + 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}, + socket + ) do + case render_balance_card(address, exchange_rate, socket) do + {:ok, rendered} -> + push(socket, "balance", %{ + balance_card: rendered, + balance: address.fetched_coin_balance.value, + fetched_coin_balance_block_number: address.fetched_coin_balance_block_number + }) + + {:noreply, socket} + + _ -> + {:noreply, socket} + end + end + + def handle_out("verification_result", result, socket) do + case result[:result] do + {:ok, _contract} -> + push(socket, "verification", %{verification_result: :ok}) + {:noreply, socket} + + {:error, result} -> + push(socket, "verification", %{verification_result: result}) + {:noreply, socket} + 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) + + push(socket, "count", %{count: BlockScoutWeb.Cldr.Number.to_string!(count, format: "#,###")}) + + {: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) + + rendered_internal_transaction = + View.render_to_string( + InternalTransactionView, + "_tile.html", + current_address: address, + internal_transaction: internal_transaction + ) + + push(socket, "internal_transaction", %{ + to_address_hash: to_string(internal_transaction.to_address_hash), + from_address_hash: to_string(internal_transaction.from_address_hash), + internal_transaction_html: rendered_internal_transaction + }) + + {:noreply, socket} + end + + def handle_out("transaction", data, socket), do: handle_transaction(data, socket, "transaction") + + 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) + + Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) + + rendered_coin_balance = + View.render_to_string( + AddressCoinBalanceView, + "_coin_balances.html", + conn: socket, + coin_balance: coin_balance + ) + + push(socket, "coin_balance", %{ + coin_balance_html: rendered_coin_balance + }) + + push_current_coin_balance(socket, block_number, coin_balance) + + {: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) + + rendered_current_coin_balance = + View.render_to_string( + AddressView, + "_current_coin_balance.html", + conn: socket, + address: Chain.hash_to_address(hash), + coin_balance: (coin_balance && coin_balance.value) || %Wei{value: Decimal.new(0)}, + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + ) + + rendered_link = + View.render_to_string( + AddressView, + "_block_link.html", + conn: socket, + block_number: block_number + ) + + push(socket, "current_coin_balance", %{ + current_coin_balance_html: rendered_current_coin_balance, + current_coin_balance_block_number_html: rendered_link, + current_coin_balance_block_number: coin_balance.block_number + }) + 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) + + rendered = + View.render_to_string( + TransactionView, + "_tile.html", + conn: socket, + current_address: address, + transaction: transaction, + burn_address_hash: @burn_address_hash + ) + + push(socket, event, %{ + to_address_hash: to_string(transaction.to_address_hash), + from_address_hash: to_string(transaction.from_address_hash), + transaction_hash: Hash.to_string(transaction.hash), + transaction_html: rendered + }) + + {: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, + :created_contract_address, + token_transfers: [:from_address, :to_address, :token] + ]) + + rendered = + View.render_to_string( + TransactionView, + "_tile.html", + current_address: address, + transaction: transaction, + burn_address_hash: @burn_address_hash, + conn: socket + ) + + push(socket, event, %{ + to_address_hash: to_string(token_transfer.to_address_hash), + from_address_hash: to_string(token_transfer.from_address_hash), + token_transfer_hash: Hash.to_string(token_transfer.transaction_hash), + token_transfer_html: rendered + }) + + {:noreply, socket} + end + + defp render_balance_card(address, exchange_rate, socket) do + Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) + + try do + rendered = + View.render_to_string( + AddressView, + "_balance_dropdown.html", + conn: socket, + address: address, + coin_balance_status: :current, + exchange_rate: exchange_rate + ) + + {:ok, rendered} + rescue + _ -> + :error + end + end +end 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 new file mode 100644 index 0000000..560a1d4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex @@ -0,0 +1,65 @@ +defmodule BlockScoutWeb.BlockChannel do + @moduledoc """ + Establishes pub/sub channel for live updates of block events. + """ + use BlockScoutWeb, :channel + + alias BlockScoutWeb.API.V2.BlockView, as: BlockViewAPI + alias BlockScoutWeb.{BlockView, ChainView} + alias Phoenix.View + alias Timex.Duration + + intercept(["new_block"]) + + def join("blocks:new_block", _params, socket) do + {:ok, %{}, socket} + end + + def join("blocks:" <> _miner_address, _params, socket) 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) + + rendered_block = + View.render_to_string( + BlockView, + "_tile.html", + block: block, + block_type: BlockView.block_type(block) + ) + + rendered_chain_block = + View.render_to_string( + ChainView, + "_block.html", + block: block + ) + + push(socket, "new_block", %{ + average_block_time: Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat), + chain_block_html: rendered_chain_block, + block_html: rendered_block, + block_number: block.number, + block_miner_hash: to_string(block.miner_hash) + }) + + {:noreply, socket} + end +end 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 new file mode 100644 index 0000000..ef5d520 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/exchange_rate_channel.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.ExchangeRateChannel do + @moduledoc """ + Establishes pub/sub channel for address page live updates. + """ + use BlockScoutWeb, :channel + + intercept(["new_rate"]) + + def join("exchange_rate:new_rate", _params, socket) 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, + market_history_data: market_history_data + }) + + {:noreply, socket} + end +end 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 new file mode 100644 index 0000000..e53ac61 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.RewardChannel do + @moduledoc """ + Establishes pub/sub channel for live updates of block reward events. + """ + use BlockScoutWeb, :channel + + alias BlockScoutWeb.TransactionView + alias Explorer.Chain + alias Phoenix.View + + intercept(["new_reward"]) + + def join("rewards:" <> address_hash, _params, socket) do + with {:ok, hash} <- Chain.string_to_address_hash(address_hash), + {:ok, address} <- Chain.hash_to_address(hash) do + {:ok, %{}, assign(socket, :current_address, address)} + 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) + + rendered_reward = + View.render_to_string( + TransactionView, + "_emission_reward_tile.html", + current_address: socket.assigns.current_address, + emission_funds: emission_funds, + validator: validator + ) + + push(socket, "new_reward", %{reward_html: rendered_reward}) + + {:noreply, socket} + end +end 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 new file mode 100644 index 0000000..56b22c0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/token_channel.ex @@ -0,0 +1,51 @@ +defmodule BlockScoutWeb.TokenChannel do + @moduledoc """ + Establishes pub/sub channel for live updates of token transfer events. + """ + use BlockScoutWeb, :channel + + alias BlockScoutWeb.Tokens.TransferView + alias Explorer.Chain + alias Explorer.Chain.Hash + alias Phoenix.View + + intercept(["token_transfer"]) + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def join("tokens:" <> _transaction_hash, _params, socket) do + {:ok, %{}, socket} + end + + 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 + Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) + + rendered_token_transfer = + View.render_to_string( + TransferView, + "_token_transfer.html", + conn: socket, + token: token_transfer.token, + token_transfer: token_transfer, + burn_address_hash: @burn_address_hash + ) + + push(socket, "token_transfer", %{ + token_transfer_hash: Hash.to_string(token_transfer.transaction_hash), + token_transfer_html: rendered_token_transfer + }) + + {:noreply, socket} + end +end 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 new file mode 100644 index 0000000..669991e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex @@ -0,0 +1,92 @@ +defmodule BlockScoutWeb.TransactionChannel do + @moduledoc """ + Establishes pub/sub channel for live updates of transaction events. + """ + use BlockScoutWeb, :channel + + alias BlockScoutWeb.TransactionView + alias Explorer.Chain + alias Explorer.Chain.Hash + alias Phoenix.View + + intercept(["pending_transaction", "transaction"]) + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def join("transactions:new_transaction", _params, socket) do + {:ok, %{}, socket} + end + + def join("transactions:new_pending_transaction", _params, socket) do + {:ok, %{}, socket} + end + + def join("transactions:stats", _params, socket) do + {:ok, %{}, socket} + end + + def join("transactions:" <> _transaction_hash, _params, socket) 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) + + rendered_transaction = + View.render_to_string( + TransactionView, + "_tile.html", + transaction: transaction, + burn_address_hash: @burn_address_hash, + conn: socket + ) + + push(socket, "pending_transaction", %{ + transaction_hash: Hash.to_string(transaction.hash), + transaction_html: rendered_transaction + }) + + {: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) + + rendered_transaction = + View.render_to_string( + TransactionView, + "_tile.html", + transaction: transaction, + burn_address_hash: @burn_address_hash, + conn: socket + ) + + push(socket, "transaction", %{ + transaction_hash: Hash.to_string(transaction.hash), + transaction_html: rendered_transaction + }) + + {:noreply, socket} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/channels/user_socket.ex b/apps/block_scout_web/lib/block_scout_web/channels/user_socket.ex new file mode 100644 index 0000000..6060428 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/user_socket.ex @@ -0,0 +1,21 @@ +defmodule BlockScoutWeb.UserSocket do + use Phoenix.Socket + use Absinthe.Phoenix.Socket, schema: BlockScoutWeb.Schema + + 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(%{"locale" => locale}, socket) do + {:ok, assign(socket, :locale, locale)} + end + + 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/channels/user_socket_v2.ex b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex new file mode 100644 index 0000000..b012b8d --- /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/checksum_address.ex b/apps/block_scout_web/lib/block_scout_web/checksum_address.ex new file mode 100644 index 0000000..660db06 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/checksum_address.ex @@ -0,0 +1,58 @@ +defmodule BlockScoutWeb.ChecksumAddress do + @moduledoc """ + Adds checksummed version of address hashes. + """ + + import Plug.Conn + + alias BlockScoutWeb.Controller, as: BlockScoutWebController + alias Explorer.Chain + alias Explorer.Chain.Address + alias Phoenix.Controller + alias Plug.Conn + + def init(opts), do: opts + + def call(%Conn{params: %{"id" => id}} = conn, _opts) do + check_checksum(conn, id, "id") + end + + def call(%Conn{params: %{"address_id" => id}} = conn, _opts) do + check_checksum(conn, id, "address_id") + end + + def call(conn, _), do: conn + + defp check_checksum(conn, id, param_name) do + if Application.get_env(:block_scout_web, :checksum_address_hashes) do + case Chain.string_to_address_hash(id) do + {:ok, address_hash} -> + checksummed_hash = Address.checksum(address_hash) + + if checksummed_hash != id do + conn = %{conn | params: Map.merge(conn.params, %{param_name => checksummed_hash})} + + path_with_checksummed_address = String.replace(conn.request_path, id, checksummed_hash) + + new_path = + if conn.query_string != "" do + path_with_checksummed_address <> "?" <> conn.query_string + else + path_with_checksummed_address + end + + conn + |> Controller.redirect(to: new_path |> BlockScoutWebController.full_path()) + |> halt + else + conn + end + + _ -> + conn + end + else + conn + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/cldr.ex b/apps/block_scout_web/lib/block_scout_web/cldr.ex new file mode 100644 index 0000000..722dbe6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/cldr.ex @@ -0,0 +1,13 @@ +defmodule BlockScoutWeb.Cldr do + @moduledoc """ + Cldr global configuration. + """ + + use Cldr, + default_locale: "en", + locales: ["en"], + gettext: BlockScoutWeb.Gettext, + generate_docs: false, + precompile_number_formats: ["#,###", "#,##0.##################", "#.#%", "#,##0"], + providers: [Cldr.Number, Cldr.Unit] +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 new file mode 100644 index 0000000..7776342 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controller.ex @@ -0,0 +1,65 @@ +defmodule BlockScoutWeb.Controller do + @moduledoc """ + Common controller error responses + """ + + import Phoenix.Controller + import Plug.Conn + + @doc """ + Renders HTML Not Found error + """ + def not_found(conn) do + conn + |> put_status(:not_found) + |> put_view(BlockScoutWeb.PageNotFoundView) + |> render(:index, token: nil) + |> halt() + end + + def unprocessable_entity(conn) do + conn + |> put_status(:unprocessable_entity) + |> put_view(BlockScoutWeb.Error422View) + |> render(:index) + |> halt() + end + + @doc """ + Checks if the request is AJAX or not. + """ + def ajax?(conn) do + case get_req_header(conn, "x-requested-with") do + [value] -> value in ["XMLHttpRequest", "xmlhttprequest"] + [] -> false + end + end + + def current_full_path(conn) do + current_path = current_path(conn) + + full_path(current_path) + end + + def full_path(path) do + url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url] + network_path = url_params[:path] + + if network_path 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 + path + end + end +end 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 0000000..56c3e44 --- /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 0000000..91f8ab5 --- /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 0000000..22ab4e2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex @@ -0,0 +1,476 @@ +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_address_fetched_coin_balance(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)), + watchlist_address_preloaded <- WatchlistAddress.preload_address_fetched_coin_balance(watchlist_address) do + conn + |> put_status(200) + |> render(:watchlist_address, %{ + watchlist_address: watchlist_address_preloaded, + 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)), + watchlist_address_preloaded <- WatchlistAddress.preload_address_fetched_coin_balance(watchlist_address) do + conn + |> put_status(200) + |> render(:watchlist_address, %{ + watchlist_address: watchlist_address_preloaded, + 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_address_fetched_coin_balance(watchlist) do + watchlist + |> 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/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 0000000..55c0b05 --- /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 0000000..78080af --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex @@ -0,0 +1,74 @@ +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: root()) + + {: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 +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 0000000..dcd8fca --- /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 0000000..a380a2c --- /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 0000000..8ca30a9 --- /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 0000000..cba3dea --- /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 0000000..5523689 --- /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 0000000..f8c5483 --- /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_by_day_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_by_day_controller.ex new file mode 100644 index 0000000..030f80a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_by_day_controller.ex @@ -0,0 +1,27 @@ +defmodule BlockScoutWeb.AddressCoinBalanceByDayController do + @moduledoc """ + Manages the grouping by day of the coin balance history of an address + """ + + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias Explorer.Chain + + def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do + with {:ok, address_hash} <- 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() + |> Enum.map(fn %{value: value} = map -> + Map.put(map, :value, Decimal.to_float(value)) + end) + + json(conn, balances_by_day) + else + _ -> + not_found(conn) + end + 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 new file mode 100644 index 0000000..6f8f220 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex @@ -0,0 +1,119 @@ +defmodule BlockScoutWeb.AddressCoinBalanceController do + @moduledoc """ + Manages the displaying of information about the coin balance history of an address + """ + + 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} + alias Explorer.Chain.{Address, Wei} + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_address_exists(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + full_options = paging_options(params) + + coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options) + + {coin_balances, next_page} = split_list_by_page(coin_balances_plus_one) + + next_page_url = + case next_page_params(next_page, coin_balances, params) do + nil -> + nil + + next_page_params -> + address_coin_balance_path( + conn, + :index, + address_hash, + Map.delete(next_page_params, "type") + ) + end + + coin_balances_json = + Enum.map(coin_balances, fn coin_balance -> + View.render_to_string( + AddressCoinBalanceView, + "_coin_balances.html", + conn: conn, + coin_balance: coin_balance + ) + end) + + json(conn, %{items: coin_balances_json, next_page_path: next_page_url}) + else + {:restricted_access, _} -> + not_found(conn) + + :not_found -> + case Chain.Hash.Address.validate(address_hash_string) do + {:ok, _} -> + json(conn, %{items: [], next_page_path: ""}) + + _ -> + not_found(conn) + end + + :error -> + unprocessable_entity(conn) + end + end + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render(conn, "index.html", + address: address, + 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)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + {:error, :not_found} -> + {:ok, address_hash} = Chain.string_to_address_hash(address_hash_string) + + address = %Chain.Address{ + hash: address_hash, + smart_contract: nil, + token: nil, + fetched_coin_balance: %Wei{value: Decimal.new(0)} + } + + case Chain.Hash.Address.validate(address_hash_string) do + {:ok, _} -> + render( + conn, + "index.html", + address: address, + 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), + tags: get_address_tags(address_hash, current_user(conn)) + ) + + _ -> + not_found(conn) + end + + :error -> + unprocessable_entity(conn) + end + end +end 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 new file mode 100644 index 0000000..ffd65b2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex @@ -0,0 +1,49 @@ +# credo:disable-for-this-file +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} + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + + def index(conn, %{"address_id" => address_hash_string} = params) do + address_options = [ + necessity_by_association: %{ + :contracts_creation_internal_transaction => :optional, + :names => :optional, + :smart_contract => :optional, + :token => :optional, + :contracts_creation_transaction => :optional + } + ] + + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + _ <- VerificationController.check_and_verify(address_hash_string), + {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do + render( + conn, + "index.html", + 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}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..7a56e64 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex @@ -0,0 +1,309 @@ +defmodule BlockScoutWeb.AddressContractVerificationController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.RPC.ContractController + alias BlockScoutWeb.Controller + alias Ecto.Changeset + alias Explorer.Chain + alias Explorer.Chain.Events.Publisher, as: EventsPublisher + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} + alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker + alias Explorer.SmartContract.Vyper.PublisherWorker, as: VyperPublisherWorker + alias Explorer.ThirdPartyIntegrations.Sourcify + + def new(conn, %{"address_id" => address_hash_string}) do + 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, + compiler_versions: compiler_versions, + evm_versions: CodeCompiler.allowed_evm_versions(), + address_hash: address_hash_string + ) + 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, + %{ + "smart_contract" => smart_contract, + "external_libraries" => external_libraries + } + ) do + Que.add(SolidityPublisherWorker, {"flattened", smart_contract, external_libraries, conn}) + + send_resp(conn, 204, "") + end + + # sobelow_skip ["Traversal.FileModule"] + def create( + conn, + %{ + "smart_contract" => smart_contract, + "file" => files, + "verification_type" => "json:standard" + } + ) do + files_array = prepare_files_array(files) + + with %Plug.Upload{path: path} <- get_one_json(files_array), + {:ok, json_input} <- File.read(path) do + Que.add(SolidityPublisherWorker, {"json_web", smart_contract, json_input, conn}) + else + _ -> + nil + end + + send_resp(conn, 204, "") + end + + def create( + conn, + %{ + "smart_contract" => smart_contract, + "verification_type" => "vyper" + } + ) do + Que.add(VyperPublisherWorker, {smart_contract["address_hash"], smart_contract, conn}) + + send_resp(conn, 204, "") + end + + def create( + conn, + %{ + "address_hash" => address_hash_string, + "file" => files, + "verification_type" => "json:metadata" + } + ) do + files_array = prepare_files_array(files) + + json_file = get_one_json(files_array) + + if json_file do + if Chain.smart_contract_fully_verified?(address_hash_string) do + EventsPublisher.broadcast( + prepare_verification_error( + "This contract already verified in Blockscout.", + address_hash_string, + conn + ), + :on_demand + ) + else + case Sourcify.check_by_address(address_hash_string) do + {:ok, _verified_status} -> + get_metadata_and_publish(address_hash_string, conn) + + _ -> + verify_and_publish(address_hash_string, files_array, conn) + end + end + else + EventsPublisher.broadcast( + prepare_verification_error( + "Please attach JSON file with metadata of contract's compilation.", + address_hash_string, + conn + ), + :on_demand + ) + end + + send_resp(conn, 204, "") + end + + def create(conn, _params) do + Que.add(SolidityPublisherWorker, {"", %{}, %{}, conn}) + + send_resp(conn, 204, "") + end + + defp verify_and_publish(address_hash_string, files_array, conn) do + with {:ok, _verified_status} <- Sourcify.verify(address_hash_string, files_array), + {:ok, _verified_status} <- Sourcify.check_by_address(address_hash_string) do + get_metadata_and_publish(address_hash_string, conn) + else + {:error, "partial"} -> + {:ok, status, metadata} = Sourcify.check_by_address_any(address_hash_string) + process_metadata_and_publish(address_hash_string, metadata, status == "partial", conn) + + {:error, %{"error" => error}} -> + EventsPublisher.broadcast( + prepare_verification_error(error, address_hash_string, conn), + :on_demand + ) + + {:error, error} -> + EventsPublisher.broadcast( + prepare_verification_error(error, address_hash_string, conn), + :on_demand + ) + + _ -> + EventsPublisher.broadcast( + prepare_verification_error("Unexpected error", address_hash_string, conn), + :on_demand + ) + end + end + + def get_metadata_and_publish(address_hash_string, conn) do + case Sourcify.get_metadata(address_hash_string) do + {:ok, verification_metadata} -> + process_metadata_and_publish(address_hash_string, verification_metadata, false, conn) + + {:error, %{"error" => error}} -> + return_sourcify_error(conn, error, address_hash_string) + end + end + + defp process_metadata_and_publish(address_hash_string, verification_metadata, is_partial, conn \\ nil) do + case Sourcify.parse_params_from_sourcify(address_hash_string, verification_metadata) do + %{ + "params_to_publish" => params_to_publish, + "abi" => abi, + "secondary_sources" => secondary_sources, + "compilation_target_file_path" => compilation_target_file_path + } -> + ContractController.publish(conn, %{ + "addressHash" => address_hash_string, + "params" => Map.put(params_to_publish, "partially_verified", is_partial), + "abi" => abi, + "secondarySources" => secondary_sources, + "compilationTargetFilePath" => compilation_target_file_path + }) + + {:error, :metadata} -> + return_sourcify_error(conn, Sourcify.no_metadata_message(), address_hash_string) + + _ -> + return_sourcify_error(conn, Sourcify.failed_verification_message(), address_hash_string) + end + end + + defp return_sourcify_error(nil, error, _address_hash_string) do + {:error, error: error} + end + + defp return_sourcify_error(conn, error, address_hash_string) do + EventsPublisher.broadcast( + prepare_verification_error(error, address_hash_string, conn), + :on_demand + ) + end + + def prepare_files_array(files) do + if is_map(files), do: Enum.map(files, fn {_, file} -> file end), else: [] + end + + defp get_one_json(files_array) do + files_array + |> Enum.filter(fn file -> file.content_type == "application/json" end) + |> 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, + {address_hash_string, + {:error, + %Changeset{ + action: :insert, + errors: [ + file: {msg, []} + ], + data: %SmartContract{address_hash: address_hash_string}, + valid?: false + }}, conn}} + ] + end + + def parse_optimization_runs(%{"runs" => runs}) do + case Integer.parse(runs) do + {integer, ""} -> integer + _ -> 200 + end + end + + def check_and_verify(address_hash_string) do + if Chain.smart_contract_fully_verified?(address_hash_string) do + {:ok, :already_fully_verified} + else + if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do + if Chain.smart_contract_verified?(address_hash_string) do + case Sourcify.check_by_address(address_hash_string) do + {:ok, _verified_status} -> + get_metadata_and_publish(address_hash_string, nil) + + _ -> + {:error, :not_verified} + end + else + case Sourcify.check_by_address_any(address_hash_string) do + {:ok, "full", metadata} -> + process_metadata_and_publish(address_hash_string, metadata, false) + + {:ok, "partial", metadata} -> + process_metadata_and_publish(address_hash_string, metadata, true) + + _ -> + {:error, :not_verified} + end + end + else + {:error, :sourcify_disabled} + end + end + end +end 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 new file mode 100644 index 0000000..632c057 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex @@ -0,0 +1,60 @@ +defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias Explorer.Chain + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler, Solidity.PublisherWorker} + + def new(conn, %{"address_id" => address_hash_string}) do + if Chain.smart_contract_fully_verified?(address_hash_string) do + 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, + compiler_versions: compiler_versions, + evm_versions: CodeCompiler.allowed_evm_versions(), + address_hash: address_hash_string + ) + end + end + + def create( + conn, + %{ + "smart_contract" => smart_contract, + "external_libraries" => external_libraries + } + ) do + Que.add(PublisherWorker, {"flattened", smart_contract, external_libraries, conn}) + + send_resp(conn, 204, "") + end + + def parse_optimization_runs(%{"runs" => runs}) do + case Integer.parse(runs) do + {integer, ""} -> integer + _ -> 200 + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex new file mode 100644 index 0000000..4b29a66 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController + alias BlockScoutWeb.Controller + alias Explorer.Chain + alias Explorer.Chain.SmartContract + alias Explorer.ThirdPartyIntegrations.Sourcify + + def new(conn, %{"address_id" => address_hash_string}) do + address_contract_path = + conn + |> address_contract_path(:index, address_hash_string) + |> Controller.full_path() + + if Chain.smart_contract_fully_verified?(address_hash_string) do + redirect(conn, to: address_contract_path) + else + case Sourcify.check_by_address(address_hash_string) do + {:ok, _verified_status} -> + VerificationController.get_metadata_and_publish(address_hash_string, conn) + redirect(conn, to: address_contract_path) + + _ -> + changeset = + SmartContract.changeset( + %SmartContract{address_hash: address_hash_string}, + %{} + ) + + render(conn, "new.html", changeset: changeset, address_hash: address_hash_string) + end + end + end +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 0000000..cdc6d44 --- /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_contract_verification_via_standard_json_input_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex new file mode 100644 index 0000000..1938269 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex @@ -0,0 +1,40 @@ +defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias Explorer.Chain + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.CompilerVersion + + 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, + compiler_versions: compiler_versions + ) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex new file mode 100644 index 0000000..ede3bc4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex @@ -0,0 +1,58 @@ +defmodule BlockScoutWeb.AddressContractVerificationVyperController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias Explorer.Chain + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.{CompilerVersion, Vyper.PublisherWorker} + + 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(:vyper) do + {:ok, compiler_versions} -> + compiler_versions + + {:error, _} -> + [] + end + + render(conn, "new.html", + changeset: changeset, + compiler_versions: compiler_versions, + address_hash: address_hash_string + ) + end + end + + def create( + conn, + %{ + "smart_contract" => smart_contract + } + ) do + Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, conn}) + + send_resp(conn, 204, "") + end + + def parse_optimization_runs(%{"runs" => runs}) do + case Integer.parse(runs) do + {integer, ""} -> integer + _ -> 200 + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex new file mode 100644 index 0000000..b90321d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex @@ -0,0 +1,226 @@ +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, + AddressView, + Controller + } + + alias Explorer.Counters.{AddressTokenTransfersCounter, AddressTransactionsCounter, AddressTransactionsGasUsageCounter} + alias Explorer.{Chain, Market} + alias Explorer.Chain.Wei + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + def index(conn, %{"type" => "JSON"} = params) do + addresses = + params + |> paging_options() + |> Chain.list_top_addresses() + + {addresses_page, next_page} = split_list_by_page(addresses) + + next_page_path = + case next_page_params(next_page, addresses_page, params) do + nil -> + nil + + next_page_params -> + address_path( + conn, + :index, + Map.delete(next_page_params, "type") + ) + end + + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + total_supply = Chain.total_supply() + + items_count_str = Map.get(params, "items_count") + + items_count = + if items_count_str do + {items_count, _} = Integer.parse(items_count_str) + items_count + else + 0 + end + + items = + addresses_page + |> Enum.with_index(1) + |> Enum.map(fn {{address, tx_count}, index} -> + View.render_to_string( + AddressView, + "_tile.html", + address: address, + index: items_count + index, + exchange_rate: exchange_rate, + total_supply: total_supply, + tx_count: tx_count + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_path + } + ) + end + + def index(conn, _params) do + total_supply = Chain.total_supply() + + render(conn, "index.html", + current_path: Controller.current_full_path(conn), + address_count: Chain.address_estimated_count(), + total_supply: total_supply + ) + end + + def show(conn, %{"id" => address_hash_string, "type" => "JSON"} = params) do + AddressTransactionController.index(conn, Map.put(params, "address_id", address_hash_string)) + end + + def show(conn, %{"id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "_show_address_transactions.html", + address: address, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + 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), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + :error -> + unprocessable_entity(conn) + + {:restricted_access, _} -> + not_found(conn) + + {:error, :not_found} -> + {:ok, address_hash} = Chain.string_to_address_hash(address_hash_string) + + address = %Chain.Address{ + hash: address_hash, + smart_contract: nil, + token: nil, + fetched_coin_balance: %Wei{value: Decimal.new(0)} + } + + case Chain.Hash.Address.validate(address_hash_string) do + {:ok, _} -> + render( + conn, + "_show_address_transactions.html", + address: address, + coin_balance_status: nil, + 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), + tags: get_address_tags(address_hash, current_user(conn)) + ) + + _ -> + not_found(conn) + end + end + end + + 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) + + 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, %{ + transaction_count: transactions_from_db, + token_transfer_count: token_transfers_from_db, + gas_usage_count: address_gas_usage_from_db, + validation_count: validation_count + }) + else + _ -> + json(conn, %{ + transaction_count: 0, + token_transfer_count: 0, + gas_usage_count: 0, + validation_count: 0 + }) + 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 new file mode 100644 index 0000000..968326c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex @@ -0,0 +1,36 @@ +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 + alias Indexer.Fetcher.CoinBalanceOnDemand + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.find_decompiled_contract_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + 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}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..bfc8ce8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex @@ -0,0 +1,130 @@ +defmodule BlockScoutWeb.AddressInternalTransactionController do + @moduledoc """ + Manages the displaying of information about internal transactions as they relate to addresses + """ + + 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} + alias Explorer.Chain.{Address, Wei} + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- + Chain.hash_to_address(address_hash, [necessity_by_association: %{:smart_contract => :optional}], false), + {: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)) + + internal_transactions_plus_one = Chain.address_to_internal_transactions(address_hash, full_options) + {internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one) + + next_page_path = + case next_page_params(next_page, internal_transactions, params) do + nil -> + nil + + next_page_params -> + address_internal_transaction_path(conn, :index, address_hash, Map.delete(next_page_params, "type")) + end + + internal_transactions_json = + Enum.map(internal_transactions, fn internal_transaction -> + View.render_to_string( + InternalTransactionView, + "_tile.html", + current_address: address, + internal_transaction: internal_transaction + ) + end) + + json(conn, %{items: internal_transactions_json, next_page_path: next_page_path}) + else + {:restricted_access, _} -> + not_found(conn) + + {:error, :not_found} -> + case Chain.Hash.Address.validate(address_hash_string) do + {:ok, _} -> + json(conn, %{items: [], next_page_path: ""}) + + _ -> + not_found(conn) + end + + :error -> + not_found(conn) + end + end + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + 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}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + {:error, :not_found} -> + {:ok, address_hash} = Chain.string_to_address_hash(address_hash_string) + + address = %Chain.Address{ + hash: address_hash, + smart_contract: nil, + token: nil, + fetched_coin_balance: %Wei{value: Decimal.new(0)} + } + + case Chain.Hash.Address.validate(address_hash_string) do + {:ok, _} -> + render( + conn, + "index.html", + address: address, + filter: params["filter"], + 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), + tags: get_address_tags(address_hash, current_user(conn)) + ) + + _ -> + not_found(conn) + end + + :error -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..742c58c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex @@ -0,0 +1,125 @@ +defmodule BlockScoutWeb.AddressLogsController do + @moduledoc """ + 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 + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + use BlockScoutWeb, :controller + + def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_address_exists(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + logs_plus_one = Chain.address_to_logs(address_hash, paging_options(params)) + {results, next_page} = split_list_by_page(logs_plus_one) + + next_page_url = + case next_page_params(next_page, results, params) do + nil -> + nil + + next_page_params -> + address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type")) + end + + items = + results + |> Enum.map(fn log -> + View.render_to_string( + AddressLogsView, + "_logs.html", + log: log, + conn: conn + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_url + } + ) + else + _ -> + not_found(conn) + end + end + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + 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}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + _ -> + not_found(conn) + end + end + + def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_address_exists(address_hash) do + topic = String.trim(topic) + + formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic + + logs_plus_one = Chain.address_to_logs(address_hash, topic: formatted_topic) + + {results, next_page} = split_list_by_page(logs_plus_one) + + next_page_url = + case next_page_params(next_page, results, params) do + nil -> + nil + + next_page_params -> + address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type")) + end + + items = + results + |> Enum.map(fn log -> + View.render_to_string( + AddressLogsView, + "_logs.html", + log: log, + conn: conn + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_url + } + ) + else + _ -> + not_found(conn) + end + end + + def search_logs(conn, _), do: not_found(conn) +end 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 new file mode 100644 index 0000000..2751e69 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex @@ -0,0 +1,92 @@ +# credo:disable-for-this-file +# +# When moving the calls to ajax, this controller became very similar to the +# `address_contract_controller`, but both are necessary until we are able to +# address a better way to organize the controllers. +# +# So, for now, I'm adding this comment to disable the credo check for this file. +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 + address_options = [ + necessity_by_association: %{ + :contracts_creation_internal_transaction => :optional, + :names => :optional, + :smart_contract => :optional, + :token => :optional, + :contracts_creation_transaction => :optional + } + ] + + 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", + 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 + _ -> + 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 new file mode 100644 index 0000000..564655a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex @@ -0,0 +1,45 @@ +# credo:disable-for-this-file +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 + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + + def index(conn, %{"address_id" => address_hash_string} = params) do + address_options = [ + necessity_by_association: %{ + :contracts_creation_internal_transaction => :optional, + :names => :optional, + :smart_contract => :optional, + :token => :optional, + :contracts_creation_transaction => :optional + } + ] + + 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), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + type: :proxy, + 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)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + _ -> + not_found(conn) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex new file mode 100644 index 0000000..9c7ab89 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex @@ -0,0 +1,50 @@ +defmodule BlockScoutWeb.AddressTokenBalanceController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias Explorer.{Chain, Market} + alias Explorer.Chain.Address + alias Indexer.Fetcher.TokenBalanceOnDemand + + def index(conn, %{"address_id" => address_hash_string} = params) do + with true <- ajax?(conn), + {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) 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() + + case AccessHelpers.restricted_access?(address_hash_string, params) do + {:ok, false} -> + conn + |> put_status(200) + |> put_layout(false) + |> render("_token_balances.html", + address_hash: Address.checksum(address_hash), + token_balances: token_balances_with_price, + conn: conn + ) + + _ -> + conn + |> put_status(200) + |> put_layout(false) + |> render("_token_balances.html", + address_hash: Address.checksum(address_hash), + token_balances: [], + conn: conn + ) + end + else + _ -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..5470034 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex @@ -0,0 +1,93 @@ +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} + alias Explorer.Chain.Address + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash, [], false), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + token_balances_plus_one = + address_hash + |> Chain.fetch_last_token_balances(paging_options(params)) + |> Market.add_price() + + {tokens, next_page} = split_list_by_page(token_balances_plus_one) + + next_page_path = + case next_page_params(next_page, tokens, params) do + nil -> + nil + + next_page_params -> + address_token_path(conn, :index, address, Map.delete(next_page_params, "type")) + end + + items = + tokens + |> Market.add_price() + |> Enum.map(fn {token_balance, token} -> + View.render_to_string( + AddressTokenView, + "_tokens.html", + token_balance: token_balance, + token: token, + address: address, + conn: conn + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_path + } + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + 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)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..eeebc32 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex @@ -0,0 +1,221 @@ +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} + alias Explorer.Chain.Address + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + import BlockScoutWeb.Chain, + only: [current_filter: 1, next_page_params: 3, paging_options: 1, split_list_by_page: 1] + + @transaction_necessity_by_association [ + 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, + [token_transfers: :token] => :optional, + [token_transfers: :to_address] => :optional, + [token_transfers: :from_address] => :optional, + [token_transfers: :token_contract_address] => :optional, + :block => :required + } + ] + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def index( + conn, + %{ + "address_id" => address_hash_string, + "address_token_id" => token_hash_string, + "type" => "JSON" + } = params + ) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, _} <- Chain.token_from_address_hash(token_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + transactions = + Chain.address_to_transactions_with_token_transfers( + address_hash, + token_hash, + paging_options(params) + ) + + {transactions_paginated, next_page} = split_list_by_page(transactions) + + next_page_path = + case next_page_params(next_page, transactions_paginated, params) do + nil -> + nil + + next_page_params -> + address_token_transfers_path( + conn, + :index, + address_hash_string, + token_hash_string, + Map.delete(next_page_params, "type") + ) + end + + transfers_json = + Enum.map(transactions_paginated, fn transaction -> + View.render_to_string( + TransactionView, + "_tile.html", + conn: conn, + transaction: transaction, + burn_address_hash: @burn_address_hash, + current_address: address + ) + end) + + json(conn, %{items: transfers_json, next_page_path: next_page_path}) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index( + conn, + %{"address_id" => address_hash_string, "address_token_id" => token_hash_string} = params + ) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, token} <- Chain.token_from_address_hash(token_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + 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)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index( + conn, + %{ + "address_id" => address_hash_string, + "type" => "JSON" + } = params + ) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {: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)) + + transactions = + Chain.address_hash_to_token_transfers( + address_hash, + options + ) + + {transactions_paginated, next_page} = split_list_by_page(transactions) + + next_page_path = + case next_page_params(next_page, transactions_paginated, params) do + nil -> + nil + + next_page_params -> + address_token_transfers_path( + conn, + :index, + address_hash_string, + Map.delete(next_page_params, "type") + ) + end + + transfers_json = + Enum.map(transactions_paginated, fn transaction -> + View.render_to_string( + TransactionView, + "_tile.html", + conn: conn, + transaction: transaction, + burn_address_hash: @burn_address_hash, + current_address: address + ) + end) + + json(conn, %{items: transfers_json, next_page_path: next_page_path}) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index( + conn, + %{"address_id" => address_hash_string} = params + ) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + 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)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..679b0f6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -0,0 +1,292 @@ +defmodule BlockScoutWeb.AddressTransactionController do + @moduledoc """ + Display all the Transactions that terminate at this Address. + """ + + 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} + + alias Explorer.Chain.{ + AddressInternalTransactionCsvExporter, + AddressLogCsvExporter, + AddressTokenTransferCsvExporter, + AddressTransactionCsvExporter, + Wei + } + + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + alias Plug.Conn + + @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 + } + ] + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do + address_options = [necessity_by_association: %{:names => :optional, :smart_contract => :optional}] + + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash, address_options, false), + {: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) + {results, next_page} = split_list_by_page(results_plus_one) + + next_page_url = + case next_page_params(next_page, results, params) do + nil -> + nil + + next_page_params -> + address_transaction_path( + conn, + :index, + address, + Map.delete(next_page_params, "type") + ) + end + + items_json = + Enum.map(results, fn result -> + case result do + {%Chain.Block.Reward{} = emission_reward, %Chain.Block.Reward{} = validator_reward} -> + View.render_to_string( + TransactionView, + "_emission_reward_tile.html", + current_address: address, + emission_funds: emission_reward, + validator: validator_reward + ) + + %Chain.Transaction{} = transaction -> + View.render_to_string( + TransactionView, + "_tile.html", + conn: conn, + current_address: address, + transaction: transaction, + burn_address_hash: @burn_address_hash + ) + end + end) + + json(conn, %{items: items_json, next_page_path: next_page_url}) + else + :error -> + unprocessable_entity(conn) + + {:restricted_access, _} -> + not_found(conn) + + {:error, :not_found} -> + case Chain.Hash.Address.validate(address_hash_string) do + {:ok, _} -> + json(conn, %{items: [], next_page_path: ""}) + + _ -> + not_found(conn) + end + end + end + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + 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), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + :error -> + unprocessable_entity(conn) + + {:restricted_access, _} -> + not_found(conn) + + {:error, :not_found} -> + {:ok, address_hash} = Chain.string_to_address_hash(address_hash_string) + + address = %Chain.Address{ + hash: address_hash, + smart_contract: nil, + token: nil, + fetched_coin_balance: %Wei{value: Decimal.new(0)} + } + + case Chain.Hash.Address.validate(address_hash_string) do + {:ok, _} -> + render( + conn, + "index.html", + address: address, + coin_balance_status: nil, + 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), + tags: get_address_tags(address_hash, current_user(conn)) + ) + + _ -> + not_found(conn) + end + end + end + + 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), + {:recaptcha, true} <- {:recaptcha, captcha_helper().recaptcha_passed?(recaptcha_response)} do + address + |> 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 + + 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, + "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 + }, + AddressTransactionCsvExporter, + "transactions.csv" + ) + end + + def internal_transactions_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 + }, + AddressInternalTransactionCsvExporter, + "internal_transactions.csv" + ) + 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 +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 new file mode 100644 index 0000000..3963c26 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex @@ -0,0 +1,100 @@ +defmodule BlockScoutWeb.AddressValidationController do + @moduledoc """ + Display all the blocks that this address validates. + """ + 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} + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Phoenix.View + + def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, _} <- Chain.find_or_insert_address_from_hash(address_hash, [], false), + {: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) + ) + + blocks_plus_one = Chain.get_blocks_validated_by_address(full_options, address_hash) + {blocks, next_page} = split_list_by_page(blocks_plus_one) + + next_page_path = + case next_page_params(next_page, blocks, params) do + nil -> + nil + + next_page_params -> + address_validation_path( + conn, + :index, + address_hash_string, + Map.delete(next_page_params, "type") + ) + end + + items = + Enum.map(blocks, fn block -> + View.render_to_string( + BlockView, + "_tile.html", + conn: conn, + block: block, + block_type: BlockView.block_type(block) + ) + end) + + json(conn, %{items: items, next_page_path: next_page_path}) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + end + end + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.find_or_insert_address_from_hash(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + 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(), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..1accce5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex @@ -0,0 +1,84 @@ +# credo:disable-for-this-file +# +# When moving the calls to ajax, this controller became very similar to the +# `address_contract_controller`, but both are necessary until we are able to +# address a better way to organize the controllers. +# +# So, for now, I'm adding this comment to disable the credo check for this file. +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 + alias Indexer.Fetcher.CoinBalanceOnDemand + + def index(conn, %{"address_id" => address_hash_string} = params) do + address_options = [ + necessity_by_association: %{ + :contracts_creation_internal_transaction => :optional, + :names => :optional, + :smart_contract => :optional, + :token => :optional, + :contracts_creation_transaction => :optional + } + ] + + 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), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + 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 + _ -> + 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 new file mode 100644 index 0000000..fc5a7c7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex @@ -0,0 +1,45 @@ +# credo:disable-for-this-file +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 + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + + def index(conn, %{"address_id" => address_hash_string} = params) do + address_options = [ + necessity_by_association: %{ + :contracts_creation_internal_transaction => :optional, + :names => :optional, + :smart_contract => :optional, + :token => :optional, + :contracts_creation_transaction => :optional + } + ] + + 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), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + address: address, + type: :proxy, + 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)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + _ -> + not_found(conn) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/admin/dashboard_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/admin/dashboard_controller.ex new file mode 100644 index 0000000..4455e0f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/admin/dashboard_controller.ex @@ -0,0 +1,7 @@ +defmodule BlockScoutWeb.Admin.DashboardController do + use BlockScoutWeb, :controller + + def index(conn, _) do + render(conn, "index.html") + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/admin/session_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/admin/session_controller.ex new file mode 100644 index 0000000..6c200e3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/admin/session_controller.ex @@ -0,0 +1,37 @@ +defmodule BlockScoutWeb.Admin.SessionController do + use BlockScoutWeb, :controller + + alias Ecto.Changeset + alias Explorer.{Accounts, Admin} + alias Explorer.Accounts.User.Authenticate + + def new(conn, _) do + changeset = Authenticate.changeset() + render(conn, "login_form.html", changeset: changeset) + end + + def create(conn, %{"authenticate" => params}) do + with {:user, {:ok, user}} <- {:user, Accounts.authenticate(params)}, + {:admin, {:ok, _}} <- {:admin, Admin.from_user(user)} do + conn + |> put_session(:user_id, user.id) + |> redirect(to: AdminRoutes.dashboard_path(conn, :index)) + else + {:user, {:error, :invalid_credentials}} -> + changeset = Authenticate.changeset(params) + render(conn, "login_form.html", changeset: changeset) + + {:user, {:error, %Changeset{} = changeset}} -> + render(conn, "login_form.html", changeset: changeset) + + {:admin, {:error, :not_found}} -> + changeset = Authenticate.changeset() + render(conn, "login_form.html", changeset: changeset) + end + end + + def create(conn, _) do + changeset = Authenticate.changeset() + render(conn, "login_form.html", changeset: changeset) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/admin/setup_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/admin/setup_controller.ex new file mode 100644 index 0000000..9005fe3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/admin/setup_controller.ex @@ -0,0 +1,90 @@ +defmodule BlockScoutWeb.Admin.SetupController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.AdminRouter.Helpers + + alias BlockScoutWeb.Endpoint + alias Explorer.Accounts.User.Registration + alias Explorer.Admin + alias Phoenix.Token + + @admin_registration_salt "admin-registration" + + plug(:redirect_to_login_if_configured) + + # Step 2: enter new admin credentials + def configure(conn, %{"state" => state}) do + case valid_state?(state) do + true -> + changeset = Registration.changeset() + render(conn, "admin_registration.html", changeset: changeset) + + false -> + render(conn, "verify.html") + end + end + + # Step 1: enter recovery key + def configure(conn, _) do + render(conn, "verify.html") + end + + # Step 1: verify recovery token + def configure_admin(conn, %{"verify" => %{"recovery_key" => key}}) do + if key == Admin.recovery_key() do + redirect(conn, to: setup_path(conn, :configure, %{state: generate_secure_token()})) + else + render(conn, "verify.html") + end + end + + # Step 2: register new admin + def configure_admin(conn, %{"state" => state, "registration" => registration}) do + with true <- valid_state?(state), + {:ok, %{user: user, admin: _admin}} <- Admin.register_owner(registration) do + conn + |> put_session(:user_id, user.id) + |> redirect(to: dashboard_path(conn, :index)) + else + false -> + render(conn, "verify.html") + + {:error, changeset} -> + render(conn, "admin_registration.html", changeset: changeset) + end + end + + # Step 1: enter recovery key + def configure_admin(conn, _) do + render(conn, "verify.html") + end + + @doc false + def generate_secure_token do + key = Admin.recovery_key() + Token.sign(Endpoint, @admin_registration_salt, key) + end + + defp valid_state?(state) do + # 5 minutes + max_age_in_seconds = 300 + opts = [max_age: max_age_in_seconds] + + case Token.verify(Endpoint, @admin_registration_salt, state, opts) do + {:ok, _} -> true + _ -> false + end + end + + defp redirect_to_login_if_configured(conn, _) do + case Admin.owner() do + {:ok, _} -> + conn + |> redirect(to: session_path(conn, :new)) + |> halt() + + _ -> + conn + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/admin/tasks_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/admin/tasks_controller.ex new file mode 100644 index 0000000..1e94fc5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/admin/tasks_controller.ex @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.Admin.TaskController do + use BlockScoutWeb, :controller + + require Logger + + alias Explorer.Chain.ContractMethod + + @ok_resp Poison.encode!(%{status: "success"}) + @not_ok_resp Poison.encode!(%{status: "failure"}) + + def create_contract_methods(conn, _) do + case ContractMethod.import_all() do + :ok -> + send_resp(conn, 200, Poison.encode!(@ok_resp)) + + {:error, error} -> + Logger.error(fn -> ["Something went wrong while creating contract methods: ", inspect(error)] end) + + send_resp(conn, 500, Poison.encode!(@not_ok_resp)) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/api_logger.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/api_logger.ex new file mode 100644 index 0000000..bc2d77d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/api_logger.ex @@ -0,0 +1,25 @@ +defmodule BlockScoutWeb.API.APILogger do + @moduledoc """ + Logger for API endpoints usage + """ + require Logger + + def log(conn) do + endpoint = + if conn.query_string do + "#{conn.request_path}?#{conn.query_string}" + else + conn.request_path + end + + Logger.debug(endpoint, + fetcher: :api + ) + end + + def message(text) do + Logger.debug(text, + fetcher: :api + ) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/eth_rpc/eth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/eth_rpc/eth_controller.ex new file mode 100644 index 0000000..178cb32 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/eth_rpc/eth_controller.ex @@ -0,0 +1,63 @@ +defmodule BlockScoutWeb.API.EthRPC.EthController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias BlockScoutWeb.API.EthRPC.View, as: EthRPCView + alias Explorer.EthRPC + + def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do + case AccessHelpers.check_rate_limit(conn) do + :ok -> + responses = EthRPC.responses(requests) + + conn + |> put_status(200) + |> put_view(EthRPCView) + |> render("responses.json", %{responses: responses}) + + :rate_limit_reached -> + AccessHelpers.handle_rate_limit_deny(conn) + end + end + + def eth_request(%{body_params: %{"_json" => request}} = conn, _) do + case AccessHelpers.check_rate_limit(conn) do + :ok -> + [response] = EthRPC.responses([request]) + + conn + |> put_status(200) + |> put_view(EthRPCView) + |> render("response.json", %{response: response}) + + :rate_limit_reached -> + AccessHelpers.handle_rate_limit_deny(conn) + end + end + + def eth_request(conn, request) do + case AccessHelpers.check_rate_limit(conn) do + :ok -> + # In the case that the JSON body is sent up w/o a json content type, + # Phoenix encodes it as a single key value pair, with the value being + # nil and the body being the key (as in a CURL request w/ no content type header) + decoded_request = + with [{single_key, nil}] <- Map.to_list(request), + {:ok, decoded} <- Jason.decode(single_key) do + decoded + else + _ -> request + end + + [response] = EthRPC.responses([decoded_request]) + + conn + |> put_status(200) + |> put_view(EthRPCView) + |> render("response.json", %{response: response}) + + :rate_limit_reached -> + AccessHelpers.handle_rate_limit_deny(conn) + end + end +end 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 new file mode 100644 index 0000000..394c301 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -0,0 +1,524 @@ +defmodule BlockScoutWeb.API.RPC.AddressController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.RPC.Helpers + alias Explorer.{Chain, Etherscan} + alias Explorer.Chain.{Address, Wei} + alias Explorer.Etherscan.{Addresses, Blocks} + alias Indexer.Fetcher.CoinBalanceOnDemand + + def listaccounts(conn, params) do + options = + params + |> optional_params() + |> Map.put_new(:page_number, 0) + |> Map.put_new(:page_size, 10) + + accounts = list_accounts(options) + + conn + |> put_status(200) + |> render(:listaccounts, %{accounts: accounts}) + end + + def eth_get_balance(conn, params) do + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:block_param, {:ok, block}} <- {:block_param, fetch_block_param(params)}, + {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:balance, {:ok, balance}} <- {:balance, Blocks.get_balance_as_of_block(address_hash, block)} do + render(conn, :eth_get_balance, %{balance: Wei.hex_format(balance)}) + else + {:address_param, :error} -> + conn + |> put_status(400) + |> render(:eth_get_balance_error, %{message: "Query parameter 'address' is required"}) + + {:format, :error} -> + conn + |> put_status(400) + |> render(:eth_get_balance_error, %{error: "Invalid address hash"}) + + {:block_param, :error} -> + conn + |> put_status(400) + |> render(:eth_get_balance_error, %{error: "Invalid block"}) + + {:balance, {:error, :not_found}} -> + conn + |> put_status(404) + |> render(:eth_get_balance_error, %{error: "Balance not found"}) + end + end + + def balance(conn, params, template \\ :balance) do + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do + addresses = hashes_to_addresses(address_hashes) + render(conn, template, %{addresses: addresses}) + else + {:address_param, :error} -> + conn + |> put_status(200) + |> render(:error, error: "Query parameter 'address' is required") + + {:format, :error} -> + conn + |> put_status(200) + |> render(:error, error: "Invalid address hash") + end + end + + def balancemulti(conn, params) do + balance(conn, params, :balancemulti) + end + + def pendingtxlist(conn, params) do + options = optional_params(params) + + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:ok, transactions} <- list_pending_transactions(address_hash, options) do + render(conn, :pendingtxlist, %{transactions: transactions}) + else + {:address_param, :error} -> + conn + |> put_status(200) + |> render(:error, error: "Query parameter 'address' is required") + + {:format, :error} -> + conn + |> put_status(200) + |> render(:error, error: "Invalid address format") + + {:error, :not_found} -> + render(conn, :error, error: "No transactions found", data: []) + end + end + + def txlist(conn, params) do + options = optional_params(params) + + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:ok, transactions} <- list_transactions(address_hash, options) do + render(conn, :txlist, %{transactions: transactions}) + else + {:address_param, :error} -> + conn + |> put_status(200) + |> render(:error, error: "Query parameter 'address' is required") + + {:format, :error} -> + conn + |> put_status(200) + |> render(:error, error: "Invalid address format") + + {_, :not_found} -> + render(conn, :error, error: "No transactions found", data: []) + end + end + + def txlistinternal(conn, params) do + case {Map.fetch(params, "txhash"), Map.fetch(params, "address")} do + {:error, :error} -> + render(conn, :error, error: "Query parameter txhash or address is required") + + {{:ok, txhash_param}, :error} -> + txlistinternal(conn, txhash_param, :txhash) + + {:error, {:ok, address_param}} -> + txlistinternal(conn, params, address_param, :address) + end + end + + def txlistinternal(conn, txhash_param, :txhash) do + with {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param), + {:ok, internal_transactions} <- list_internal_transactions(transaction_hash) do + render(conn, :txlistinternal, %{internal_transactions: internal_transactions}) + else + {:format, :error} -> + render(conn, :error, error: "Invalid txhash format") + + {:error, :not_found} -> + render(conn, :error, error: "No internal transactions found", data: []) + end + end + + def txlistinternal(conn, params, address_param, :address) do + options = optional_params(params) + + with {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:ok, internal_transactions} <- list_internal_transactions(address_hash, options) do + render(conn, :txlistinternal, %{internal_transactions: internal_transactions}) + else + {:format, :error} -> + render(conn, :error, error: "Invalid address format") + + {_, :not_found} -> + render(conn, :error, error: "No internal transactions found", data: []) + end + end + + def tokentx(conn, params) do + options = optional_params(params) + + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:contract_address, {:ok, contract_address_hash}} <- to_contract_address_hash(params["contractaddress"]), + {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:ok, token_transfers} <- list_token_transfers(address_hash, contract_address_hash, options) do + render(conn, :tokentx, %{token_transfers: token_transfers}) + else + {:address_param, :error} -> + render(conn, :error, error: "Query parameter address is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid address format") + + {:contract_address, :error} -> + render(conn, :error, error: "Invalid contract address format") + + {_, :not_found} -> + render(conn, :error, error: "No token transfers found", data: []) + end + end + + @tokenbalance_required_params ~w(contractaddress address) + + def tokenbalance(conn, params) do + with {:required_params, {:ok, fetched_params}} <- fetch_required_params(params, @tokenbalance_required_params), + {:format, {:ok, validated_params}} <- to_valid_format(fetched_params, :tokenbalance) do + token_balance = get_token_balance(validated_params) + render(conn, "tokenbalance.json", %{token_balance: token_balance}) + else + {:required_params, {:error, missing_params}} -> + error = "Required query parameters missing: #{Enum.join(missing_params, ", ")}" + render(conn, :error, error: error) + + {:format, {:error, param}} -> + render(conn, :error, error: "Invalid #{param} format") + end + end + + def tokenlist(conn, params) do + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:ok, token_list} <- list_tokens(address_hash) do + render(conn, :token_list, %{token_list: token_list}) + else + {:address_param, :error} -> + render(conn, :error, error: "Query parameter address is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid address format") + + {_, :not_found} -> + render(conn, :error, error: "No tokens found", data: []) + end + end + + def getminedblocks(conn, params) do + options = Helpers.put_pagination_options(%{}, params) + + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:ok, blocks} <- list_blocks(address_hash, options) do + render(conn, :getminedblocks, %{blocks: blocks}) + else + {:address_param, :error} -> + render(conn, :error, error: "Query parameter 'address' is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid address format") + + {_, :not_found} -> + render(conn, :error, error: "No blocks found", data: []) + end + end + + @doc """ + Sanitizes optional params. + + """ + @spec optional_params(map()) :: map() + def optional_params(params) do + %{} + |> put_order_by_direction(params) + |> Helpers.put_pagination_options(params) + |> put_block(params, "start_block") + |> put_block(params, "end_block") + |> put_filter_by(params) + |> put_timestamp(params, "start_timestamp") + |> put_timestamp(params, "end_timestamp") + end + + @doc """ + Fetches required params. Returns error tuple if required params are missing. + + """ + @spec fetch_required_params(map(), list()) :: {:required_params, {:ok, map()} | {:error, [String.t(), ...]}} + def fetch_required_params(params, required_params) do + fetched_params = Map.take(params, required_params) + + result = + if all_of_required_keys_found?(fetched_params, required_params) do + {:ok, fetched_params} + else + missing_params = get_missing_required_params(fetched_params, required_params) + {:error, missing_params} + end + + {:required_params, result} + end + + defp fetch_block_param(%{"block" => "latest"}), do: {:ok, :latest} + defp fetch_block_param(%{"block" => "earliest"}), do: {:ok, :earliest} + defp fetch_block_param(%{"block" => "pending"}), do: {:ok, :pending} + + defp fetch_block_param(%{"block" => string_integer}) when is_bitstring(string_integer) do + case Integer.parse(string_integer) do + {integer, ""} -> {:ok, integer} + _ -> :error + end + end + + defp fetch_block_param(%{"block" => _block}), do: :error + defp fetch_block_param(_), do: {:ok, :latest} + + defp to_valid_format(params, :tokenbalance) do + result = + with {:ok, contract_address_hash} <- to_address_hash(params, "contractaddress"), + {:ok, address_hash} <- to_address_hash(params, "address") do + {:ok, %{contract_address_hash: contract_address_hash, address_hash: address_hash}} + else + {:error, _param_key} = error -> error + end + + {:format, result} + end + + defp all_of_required_keys_found?(fetched_params, required_params) do + Enum.all?(required_params, &Map.has_key?(fetched_params, &1)) + end + + defp get_missing_required_params(fetched_params, required_params) do + fetched_keys = fetched_params |> Map.keys() |> MapSet.new() + + required_params + |> MapSet.new() + |> MapSet.difference(fetched_keys) + |> MapSet.to_list() + end + + defp fetch_address(params) do + {:address_param, Map.fetch(params, "address")} + end + + defp to_address_hashes(address_param) when is_binary(address_param) do + address_param + |> String.split(",") + |> Enum.take(20) + |> to_address_hashes() + end + + defp to_address_hashes(address_param) when is_list(address_param) do + address_hashes = address_param_to_address_hashes(address_param) + + if any_errors?(address_hashes) do + {:format, :error} + else + {:format, {:ok, address_hashes}} + end + end + + defp address_param_to_address_hashes(address_param) do + Enum.map(address_param, fn single_address -> + case Chain.string_to_address_hash(single_address) do + {:ok, address_hash} -> address_hash + :error -> :error + end + end) + end + + defp any_errors?(address_hashes) do + Enum.any?(address_hashes, &(&1 == :error)) + end + + defp list_accounts(%{page_number: page_number, page_size: page_size}) do + offset = (max(page_number, 1) - 1) * page_size + + # limit is just page_size + offset + |> Addresses.list_ordered_addresses(page_size) + |> trigger_balances_and_add_status() + end + + defp hashes_to_addresses(address_hashes) do + address_hashes + |> Chain.hashes_to_addresses() + |> trigger_balances_and_add_status() + |> add_not_found_addresses(address_hashes) + end + + defp add_not_found_addresses(addresses, hashes) do + found_hashes = MapSet.new(addresses, & &1.hash) + + hashes + |> MapSet.new() + |> MapSet.difference(found_hashes) + |> hashes_to_addresses(:not_found) + |> Enum.concat(addresses) + end + + defp hashes_to_addresses(hashes, :not_found) do + Enum.map(hashes, fn hash -> + %Address{ + hash: hash, + fetched_coin_balance: %Wei{value: 0} + } + end) + end + + defp trigger_balances_and_add_status(addresses) do + Enum.map(addresses, fn address -> + case CoinBalanceOnDemand.trigger_fetch(address) do + :current -> + %{address | stale?: false} + + _ -> + %{address | stale?: true} + end + end) + end + + defp to_contract_address_hash(nil), do: {:contract_address, {:ok, nil}} + + defp to_contract_address_hash(address_hash_string) do + {:contract_address, Chain.string_to_address_hash(address_hash_string)} + end + + defp to_address_hash(address_hash_string) do + {:format, Chain.string_to_address_hash(address_hash_string)} + end + + defp to_address_hash(params, param_key) do + case Chain.string_to_address_hash(params[param_key]) do + {:ok, address_hash} -> {:ok, address_hash} + :error -> {:error, param_key} + end + end + + defp to_transaction_hash(transaction_hash_string) do + {:format, Chain.string_to_transaction_hash(transaction_hash_string)} + end + + defp put_order_by_direction(options, params) do + case params do + %{"sort" => sort} when sort in ["asc", "desc"] -> + order_by_direction = String.to_existing_atom(sort) + Map.put(options, :order_by_direction, order_by_direction) + + _ -> + options + end + end + + # 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 + %{"filter_by" => filter_by} when filter_by in ["from", "to"] -> + Map.put(options, String.to_atom("filter_by"), filter_by) + + _ -> + options + end + end + + def put_timestamp({:ok, options}, params, timestamp_param_key) do + options = put_timestamp(options, params, timestamp_param_key) + {:ok, options} + end + + # 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 + end + end + + defp list_transactions(address_hash, options) do + case Etherscan.list_transactions(address_hash, options) do + [] -> {:error, :not_found} + transactions -> {:ok, transactions} + end + end + + defp list_pending_transactions(address_hash, options) do + case Etherscan.list_pending_transactions(address_hash, options) do + [] -> {:error, :not_found} + pending_transactions -> {:ok, pending_transactions} + end + end + + defp list_internal_transactions(transaction_hash) do + case Etherscan.list_internal_transactions(transaction_hash) do + [] -> {:error, :not_found} + internal_transactions -> {:ok, internal_transactions} + end + end + + defp list_internal_transactions(address_hash, options) do + case Etherscan.list_internal_transactions(address_hash, options) do + [] -> {:error, :not_found} + internal_transactions -> {:ok, internal_transactions} + end + end + + defp list_token_transfers(address_hash, contract_address_hash, options) do + case Etherscan.list_token_transfers(address_hash, contract_address_hash, options) do + [] -> {:error, :not_found} + token_transfers -> {:ok, token_transfers} + end + end + + defp list_blocks(address_hash, options) do + case Etherscan.list_blocks(address_hash, options) do + [] -> {:error, :not_found} + blocks -> {:ok, blocks} + end + end + + defp get_token_balance(%{contract_address_hash: contract_address_hash, address_hash: address_hash}) do + case Etherscan.get_token_balance(contract_address_hash, address_hash) do + nil -> 0 + token_balance -> token_balance.value + end + end + + defp list_tokens(address_hash) do + case Etherscan.list_tokens(address_hash) do + [] -> {:error, :not_found} + token_list -> {:ok, token_list} + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex new file mode 100644 index 0000000..68256be --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -0,0 +1,60 @@ +defmodule BlockScoutWeb.API.RPC.BlockController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Chain, as: ChainWeb + alias Explorer.Chain + alias Explorer.Chain.Cache.BlockNumber + + def getblockreward(conn, params) do + with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, + {:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number), + {:ok, block} <- Chain.number_to_block(block_number) do + reward = Chain.block_reward(block_number) + + render(conn, :block_reward, block: block, reward: reward) + else + {:block_param, :error} -> + render(conn, :error, error: "Query parameter 'blockno' is required") + + {:error, :invalid} -> + render(conn, :error, error: "Invalid block number") + + {:error, :not_found} -> + render(conn, :error, error: "Block does not exist") + end + end + + def getblocknobytime(conn, params) do + from_api = true + + with {:timestamp_param, {:ok, unsafe_timestamp}} <- {:timestamp_param, Map.fetch(params, "timestamp")}, + {:closest_param, {:ok, unsafe_closest}} <- {:closest_param, Map.fetch(params, "closest")}, + {:ok, timestamp} <- ChainWeb.param_to_block_timestamp(unsafe_timestamp), + {:ok, closest} <- ChainWeb.param_to_block_closest(unsafe_closest), + {:ok, block_number} <- Chain.timestamp_to_block_number(timestamp, closest, from_api) do + render(conn, block_number: block_number) + else + {:timestamp_param, :error} -> + render(conn, :error, error: "Query parameter 'timestamp' is required") + + {:closest_param, :error} -> + render(conn, :error, error: "Query parameter 'closest' is required") + + {:error, :invalid_timestamp} -> + render(conn, :error, error: "Invalid `timestamp` param") + + {:error, :invalid_closest} -> + render(conn, :error, error: "Invalid `closest` param") + + {:error, :not_found} -> + render(conn, :error, error: "Block does not exist") + end + end + + def eth_block_number(conn, params) do + id = Map.get(params, "id", 1) + max_block_number = BlockNumber.get_max() + + render(conn, :eth_block_number, number: max_block_number, id: id) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex new file mode 100644 index 0000000..cc19805 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -0,0 +1,611 @@ +defmodule BlockScoutWeb.API.RPC.ContractController do + use BlockScoutWeb, :controller + + require Logger + + alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController + alias BlockScoutWeb.API.RPC.{AddressController, Helpers} + alias Explorer.Chain + alias Explorer.Chain.Events.Publisher, as: EventsPublisher + 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 + alias Explorer.ThirdPartyIntegrations.Sourcify + + @smth_went_wrong "Something went wrong while publishing the contract" + @verified "Smart-contract already verified." + @invalid_address "Invalid address hash" + @invalid_args "Invalid args format" + @address_required "Query parameter address is required" + + def verify(conn, %{"addressHash" => address_hash} = params) do + with {:params, {:ok, fetched_params}} <- {:params, fetch_verify_params(params)}, + {:format, {:ok, casted_address_hash}} <- to_address_hash(address_hash), + {:params, external_libraries} <- + {:params, fetch_external_libraries(params)}, + {:publish, {:ok, _}} <- + {:publish, Publisher.publish(address_hash, fetched_params, external_libraries)} do + address = Contracts.address_hash_to_address_with_source_code(casted_address_hash) + + render(conn, :verify, %{contract: address}) + else + {:publish, + {:error, + %Ecto.Changeset{ + errors: [ + address_hash: + {"has already been taken", + [ + constraint: :unique, + constraint_name: "smart_contracts_address_hash_index" + ]} + ] + }}} -> + render(conn, :error, error: @verified) + + {:publish, {:error, error}} -> + Logger.error(fn -> + [ + @smth_went_wrong, + ": ", + inspect(error) + ] + end) + + render(conn, :error, error: "#{@smth_went_wrong}: #{inspect(error.errors)}") + + {:publish, error} -> + Logger.error(fn -> + [ + @smth_went_wrong, + ": ", + inspect(error) + ] + end) + + render(conn, :error, error: @smth_went_wrong) + + {:format, :error} -> + render(conn, :error, error: @invalid_address) + + {:params, {:error, error}} -> + render(conn, :error, error: error) + end + end + + def verify_via_sourcify(conn, %{"addressHash" => address_hash} = input) do + files = + if Map.has_key?(input, "files") do + input["files"] + else + [] + end + + if Chain.smart_contract_fully_verified?(address_hash) do + render(conn, :error, error: @verified) + else + case Sourcify.check_by_address(address_hash) do + {:ok, _verified_status} -> + get_metadata_and_publish(address_hash, conn) + + _ -> + with {:ok, files_array} <- prepare_params(files), + {:ok, validated_files} <- validate_files(files_array) do + verify_and_publish(address_hash, validated_files, conn) + else + {:error, error} -> + render(conn, :error, error: error) + + _ -> + render(conn, :error, error: "Invalid body") + end + end + end + end + + def verifysourcecode( + conn, + %{ + "codeformat" => "solidity-standard-json-input", + "contractaddress" => address_hash, + "sourceCode" => json_input + } = params + ) do + with {:check_verified_status, false} <- + {:check_verified_status, Chain.smart_contract_fully_verified?(address_hash)}, + {: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, {"json_api", fetched_params, json_input, uid}) + + render(conn, :show, %{result: uid}) + else + {:check_verified_status, true} -> + render(conn, :error, error: @verified, data: @verified) + + {:format, :error} -> + render(conn, :error, error: @invalid_address, data: @invalid_address) + + {:params, {:error, error}} -> + render(conn, :error, error: error, data: error) + end + end + + def verifysourcecode(conn, %{"codeformat" => "solidity-standard-json-input"}) do + render(conn, :error, error: "Missing sourceCode or contractaddress fields") + end + + def checkverifystatus(conn, %{"guid" => guid}) do + case VerificationStatus.fetch_status(guid) do + :pending -> + render(conn, :show, %{result: "Pending in queue"}) + + :pass -> + render(conn, :show, %{result: "Pass - Verified"}) + + :fail -> + render(conn, :show, %{result: "Fail - Unable to verify"}) + + :unknown_uid -> + render(conn, :show, %{result: "Unknown UID"}) + end + end + + defp prepare_params(files) when is_struct(files) do + {:error, @invalid_args} + end + + defp prepare_params(files) when is_map(files) do + {:ok, VerificationController.prepare_files_array(files)} + end + + defp prepare_params(files) when is_list(files) do + {:ok, files} + end + + defp prepare_params(_arg) do + {:error, @invalid_args} + end + + defp validate_files(files) do + if length(files) < 2 do + {:error, "You should attach at least 2 files"} + else + files_array = + files + |> Enum.filter(fn file -> validate_filename(file.filename) end) + + jsons = + files_array + |> Enum.filter(fn file -> Helper.json_file?(file.filename) end) + + sols = + files_array + |> Enum.filter(fn file -> Helper.sol_file?(file.filename) end) + + if length(jsons) > 0 and length(sols) > 0 do + {:ok, files_array} + else + {:error, "You should attach at least one *.json and one *.sol files"} + end + end + end + + defp validate_filename(filename) do + case List.last(String.split(String.downcase(filename), ".")) do + "sol" -> + true + + "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} -> + case Sourcify.parse_params_from_sourcify(address_hash_string, verification_metadata) do + %{"params_to_publish" => params_to_publish, "abi" => abi, "secondary_sources" => secondary_sources} -> + publish_and_handle_response_without_broadcast( + address_hash_string, + params_to_publish, + abi, + secondary_sources, + conn + ) + + {:error, :metadata} -> + render(conn, :error, error: Sourcify.no_metadata_message()) + + _ -> + render(conn, :error, error: Sourcify.failed_verification_message()) + end + + {:error, %{"error" => error}} -> + render(conn, :error, error: error) + end + end + + defp publish_and_handle_response_without_broadcast( + address_hash_string, + params_to_publish, + abi, + secondary_sources, + conn + ) do + case publish_without_broadcast(%{ + "addressHash" => address_hash_string, + "params" => params_to_publish, + "abi" => abi, + "secondarySources" => secondary_sources + }) do + {:ok, _contract} -> + {:format, {:ok, address_hash}} = to_address_hash(address_hash_string) + address = Contracts.address_hash_to_address_with_source_code(address_hash) + render(conn, :verify, %{contract: address}) + + {:error, changeset} -> + render(conn, :error, error: changeset) + end + end + + defp verify_and_publish(address_hash_string, files_array, conn) do + case Sourcify.verify(address_hash_string, files_array) do + {:ok, _verified_status} -> + case Sourcify.check_by_address(address_hash_string) do + {:ok, _verified_status} -> + get_metadata_and_publish(address_hash_string, conn) + + {:error, %{"error" => error}} -> + render(conn, :error, error: error) + + {:error, error} -> + render(conn, :error, error: error) + end + + {:error, %{"error" => error}} -> + render(conn, :error, error: error) + + {:error, error} -> + render(conn, :error, error: error) + end + end + + def verify_vyper_contract(conn, %{"addressHash" => address_hash} = params) do + with {:params, {:ok, fetched_params}} <- {:params, fetch_vyper_verify_params(params)}, + {:format, {:ok, casted_address_hash}} <- to_address_hash(address_hash), + {:publish, {:ok, _}} <- + {:publish, VyperPublisher.publish(address_hash, fetched_params)} do + address = Contracts.address_hash_to_address_with_source_code(casted_address_hash) + + render(conn, :verify, %{contract: address}) + else + {:publish, + {:error, + %Ecto.Changeset{ + errors: [ + address_hash: + {"has already been taken", + [ + constraint: :unique, + constraint_name: "smart_contracts_address_hash_index" + ]} + ] + }}} -> + render(conn, :error, error: @verified) + + {:publish, _} -> + render(conn, :error, error: @smth_went_wrong) + + {:format, :error} -> + render(conn, :error, error: @invalid_address) + + {:params, {:error, error}} -> + render(conn, :error, error: error) + end + end + + def publish_without_broadcast( + %{"addressHash" => address_hash, "abi" => abi, "compilationTargetFilePath" => file_path} = input + ) do + params = proccess_params(input) + + address_hash + |> Publisher.publish_smart_contract(params, abi, file_path) + |> proccess_response() + end + + def publish_without_broadcast(%{"addressHash" => address_hash, "abi" => abi} = input) do + params = proccess_params(input) + + address_hash + |> Publisher.publish_smart_contract(params, abi) + |> proccess_response() + end + + def publish(nil, %{"addressHash" => _address_hash} = input) do + publish_without_broadcast(input) + end + + def publish(conn, %{"addressHash" => address_hash} = input) do + result = publish_without_broadcast(input) + + EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand) + end + + def proccess_params(input) do + if Map.has_key?(input, "secondarySources") do + input["params"] + |> Map.put("secondary_sources", Map.get(input, "secondarySources")) + else + input["params"] + end + end + + def proccess_response(response) do + case response do + {:ok, _contract} = result -> + result + + {:error, changeset} -> + {:error, changeset} + end + end + + def listcontracts(conn, params) do + with pagination_options <- Helpers.put_pagination_options(%{}, params), + {:params, {:ok, options}} <- {:params, add_filters(pagination_options, params)} do + options_with_defaults = + options + |> Map.put_new(:page_number, 0) + |> Map.put_new(:page_size, 10) + + contracts = list_contracts(options_with_defaults) + + conn + |> put_status(200) + |> render(:listcontracts, %{contracts: contracts}) + else + {:params, {:error, error}} -> + conn + |> put_status(400) + |> render(:error, error: error) + end + end + + def getabi(conn, params) do + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hash}} <- to_address_hash(address_param), + {:contract, {:ok, contract}} <- to_smart_contract(address_hash) do + render(conn, :getabi, %{abi: contract.abi}) + else + {:address_param, :error} -> + render(conn, :error, error: @address_required) + + {:format, :error} -> + render(conn, :error, error: @invalid_address) + + {:contract, :not_found} -> + render(conn, :error, error: "Contract source code not verified") + end + end + + def getsourcecode(conn, params) do + with {:address_param, {:ok, address_param}} <- fetch_address(params), + {:format, {:ok, address_hash}} <- to_address_hash(address_param) do + _ = VerificationController.check_and_verify(address_param) + address = Contracts.address_hash_to_address_with_source_code(address_hash) + + render(conn, :getsourcecode, %{ + contract: address || %Address{hash: address_hash, smart_contract: nil} + }) + else + {:address_param, :error} -> + render(conn, :error, error: @address_required) + + {:format, :error} -> + render(conn, :error, error: @invalid_address) + end + end + + defp list_contracts(%{page_number: page_number, page_size: page_size} = opts) do + offset = (max(page_number, 1) - 1) * page_size + + case Map.get(opts, :filter) do + :verified -> + Contracts.list_verified_contracts(page_size, offset, opts) + + :decompiled -> + not_decompiled_with_version = Map.get(opts, :not_decompiled_with_version) + Contracts.list_decompiled_contracts(page_size, offset, not_decompiled_with_version) + + :unverified -> + Contracts.list_unordered_unverified_contracts(page_size, offset) + + :not_decompiled -> + Contracts.list_unordered_not_decompiled_contracts(page_size, offset) + + :empty -> + Contracts.list_empty_contracts(page_size, offset) + + _ -> + Contracts.list_contracts(page_size, offset) + end + end + + defp add_filters(options, params) do + options + |> add_filter(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 + with {:param, {:ok, value}} <- {:param, Map.fetch(params, "filter")}, + {:validation, {:ok, filter}} <- {:validation, contracts_filter(value)} do + {:ok, Map.put(options, :filter, filter)} + else + {:param, :error} -> {:ok, options} + {:validation, {:error, error}} -> {:error, error} + end + end + + 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_param(options, _params, _key) do + options + end + + defp contracts_filter(nil), do: {:ok, nil} + defp contracts_filter(1), do: {:ok, :verified} + defp contracts_filter(2), do: {:ok, :decompiled} + defp contracts_filter(3), do: {:ok, :unverified} + defp contracts_filter(4), do: {:ok, :not_decompiled} + defp contracts_filter(5), do: {:ok, :empty} + defp contracts_filter("verified"), do: {:ok, :verified} + defp contracts_filter("decompiled"), do: {:ok, :decompiled} + defp contracts_filter("unverified"), do: {:ok, :unverified} + defp contracts_filter("not_decompiled"), do: {:ok, :not_decompiled} + defp contracts_filter("empty"), do: {:ok, :empty} + + defp contracts_filter(filter) when is_bitstring(filter) do + case Integer.parse(filter) do + {number, ""} -> contracts_filter(number) + _ -> {:error, contracts_filter_error_message(filter)} + end + end + + defp contracts_filter(filter), do: {:error, contracts_filter_error_message(filter)} + + defp contracts_filter_error_message(filter) do + "#{filter} is not a valid value for `filter`. Please use one of: verified, decompiled, unverified, not_decompiled, 1, 2, 3, 4." + end + + defp fetch_address(params) do + {:address_param, Map.fetch(params, "address")} + end + + defp to_address_hash(address_hash_string) do + {:format, Chain.string_to_address_hash(address_hash_string)} + end + + defp to_smart_contract(address_hash) do + _ = VerificationController.check_and_verify(Hash.to_string(address_hash)) + + result = + case Chain.address_hash_to_smart_contract(address_hash) do + nil -> + :not_found + + contract -> + {:ok, SmartContract.preload_decompiled_smart_contract(contract)} + end + + {:contract, result} + end + + defp fetch_verify_params(params) do + {:ok, %{}} + |> required_param(params, "addressHash", "address_hash") + |> required_param(params, "name", "name") + |> required_param(params, "compilerVersion", "compiler_version") + |> required_param(params, "optimization", "optimization") + |> required_param(params, "contractSourceCode", "contract_source_code") + |> optional_param(params, "evmVersion", "evm_version") + |> optional_param(params, "constructorArguments", "constructor_arguments") + |> optional_param(params, "autodetectConstructorArguments", "autodetect_constructor_args") + |> optional_param(params, "optimizationRuns", "optimization_runs") + |> parse_optimization_runs() + end + + defp fetch_vyper_verify_params(params) do + {:ok, %{}} + |> required_param(params, "addressHash", "address_hash") + |> required_param(params, "name", "name") + |> required_param(params, "compilerVersion", "compiler_version") + |> required_param(params, "contractSourceCode", "contract_source_code") + |> optional_param(params, "constructorArguments", "constructor_arguments") + end + + defp fetch_verifysourcecode_params(params) do + {:ok, %{}} + |> required_param(params, "contractaddress", "address_hash") + |> required_param(params, "contractname", "name") + |> required_param(params, "compilerversion", "compiler_version") + |> optional_param(params, "constructorArguements", "constructor_arguments") + |> optional_param(params, "constructorArguments", "constructor_arguments") + end + + defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do + case Integer.parse(runs) do + {runs_int, _} -> + {:ok, Map.put(opts, "optimization_runs", runs_int)} + + _ -> + {:ok, Map.put(opts, "optimization_runs", 200)} + end + end + + defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_integer(runs) do + {:ok, opts} + end + + defp parse_optimization_runs({:ok, opts}) do + {:ok, Map.put(opts, "optimization_runs", 200)} + end + + defp parse_optimization_runs(other), do: other + + defp fetch_external_libraries(params) do + 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") + + acc + |> Map.put("library#{number}_name", library_name) + |> Map.put("library#{number}_address", library_address) + + :error -> + acc + end + end) + end + + defp required_param({:error, _} = error, _, _, _), do: error + + defp required_param({:ok, map}, params, key, new_key) do + case Map.fetch(params, key) do + {:ok, value} -> + {:ok, Map.put(map, new_key, value)} + + :error -> + {:error, "#{key} is required."} + end + end + + defp optional_param({:error, _} = error, _, _, _), do: error + + defp optional_param({:ok, map}, params, key, new_key) do + case Map.fetch(params, key) do + {:ok, value} -> + {:ok, Map.put(map, new_key, value)} + + :error -> + {:ok, map} + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helpers.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helpers.ex new file mode 100644 index 0000000..1196af9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helpers.ex @@ -0,0 +1,42 @@ +defmodule BlockScoutWeb.API.RPC.Helpers do + @moduledoc """ + Small helpers for RPC api controllers. + """ + alias Explorer.Etherscan + + def put_pagination_options(options, params) do + options + |> put_page_option(params) + |> put_offset_option(params) + end + + def put_page_option(options, %{"page" => page}) do + case Integer.parse(page) do + {page_number, ""} when page_number > 0 -> + Map.put(options, :page_number, page_number) + + _ -> + options + end + end + + def put_page_option(options, _), do: options + + def put_offset_option(options, %{"offset" => offset}) do + with {page_size, ""} when page_size > 0 <- Integer.parse(offset), + :ok <- validate_max_page_size(page_size) do + Map.put(options, :page_size, page_size) + else + _ -> + options + end + end + + def put_offset_option(options, _) do + options + end + + defp validate_max_page_size(page_size) do + if page_size <= Etherscan.page_size_max(), do: :ok, else: :error + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/logs_controller.ex new file mode 100644 index 0000000..388b99d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/logs_controller.ex @@ -0,0 +1,238 @@ +defmodule BlockScoutWeb.API.RPC.LogsController do + use BlockScoutWeb, :controller + + alias Explorer.{Chain, Etherscan} + + def getlogs(conn, params) do + with {:required_params, {:ok, fetched_params}} <- fetch_required_params(params), + {:format, {:ok, validated_params}} <- to_valid_format(fetched_params), + {:ok, logs} <- list_logs(validated_params) do + render(conn, :getlogs, %{logs: logs}) + else + {:required_params, {:error, missing_params}} -> + error = "Required query parameters missing: #{Enum.join(missing_params, ", ")}" + render(conn, :error, error: error) + + {:format, {:error, param}} -> + render(conn, :error, error: "Invalid #{param} format") + + {:error, :not_found} -> + render(conn, :error, error: "No logs found", data: []) + end + end + + # Interpretation of `@maybe_required_params`: + # + # If a pair of `topic{x}` params is provided, then the corresponding + # `topic{x}_{x}_opr` param is required. + # + # For example, if "topic0" and "topic1" are provided, then "topic0_1_opr" is + # required. + # + @maybe_required_params %{ + ["topic0", "topic1"] => "topic0_1_opr", + ["topic0", "topic2"] => "topic0_2_opr", + ["topic0", "topic3"] => "topic0_3_opr", + ["topic1", "topic2"] => "topic1_2_opr", + ["topic1", "topic3"] => "topic1_3_opr", + ["topic2", "topic3"] => "topic2_3_opr" + } + + @required_params %{ + # all_of: all of these parameters are required + all_of: ["fromBlock", "toBlock"], + # one_of: at least one of these parameters is required + one_of: ["address", "topic0", "topic1", "topic2", "topic3"] + } + + @doc """ + Fetches required params. Returns error tuple if required params are missing. + + """ + @spec fetch_required_params(map()) :: {:required_params, {:ok, map()} | {:error, [String.t(), ...]}} + def fetch_required_params(params) do + all_of_params = fetch_required_params(params, :all_of) + one_of_params = fetch_required_params(params, :one_of) + maybe_params = fetch_required_params(params, :maybe) + + result = + case {all_of_params, one_of_params, maybe_params} do + {{:error, missing_params}, {:error, _}, _} -> + {:error, Enum.concat(missing_params, ["address and/or topic{x}"])} + + {{:error, missing_params}, {:ok, _}, _} -> + {:error, missing_params} + + {{:ok, _}, {:error, _}, _} -> + {:error, ["address and/or topic{x}"]} + + {{:ok, _}, {:ok, _}, {:error, missing_params}} -> + {:error, missing_params} + + {{:ok, all_of_params}, {:ok, one_of_params}, {:ok, maybe_params}} -> + fetched_params = + all_of_params + |> Map.merge(one_of_params) + |> Map.merge(maybe_params) + + {:ok, fetched_params} + end + + {:required_params, result} + end + + @doc """ + Prepares params for processing. Returns error tuple if invalid format is + found. + + """ + @spec to_valid_format(map()) :: {:format, {:ok, map()} | {:error, String.t()}} + def to_valid_format(params) do + result = + with {:ok, from_block} <- to_block_number(params, "fromBlock"), + {:ok, to_block} <- to_block_number(params, "toBlock"), + {:ok, address_hash} <- to_address_hash(params["address"]), + :ok <- validate_topic_operators(params) do + validated_params = %{ + from_block: from_block, + to_block: to_block, + address_hash: address_hash, + first_topic: params["topic0"], + second_topic: params["topic1"], + third_topic: params["topic2"], + fourth_topic: params["topic3"], + topic0_1_opr: params["topic0_1_opr"], + topic0_2_opr: params["topic0_2_opr"], + topic0_3_opr: params["topic0_3_opr"], + topic1_2_opr: params["topic1_2_opr"], + topic1_3_opr: params["topic1_3_opr"], + topic2_3_opr: params["topic2_3_opr"] + } + + {:ok, validated_params} + else + {:error, param_key} -> + {:error, param_key} + end + + {:format, result} + end + + defp fetch_required_params(params, :all_of) do + fetched_params = Map.take(params, @required_params.all_of) + + if all_of_required_keys_found?(fetched_params) do + {:ok, fetched_params} + else + missing_params = get_missing_required_params(fetched_params, :all_of) + {:error, missing_params} + end + end + + defp fetch_required_params(params, :one_of) do + fetched_params = Map.take(params, @required_params.one_of) + found_keys = Map.keys(fetched_params) + + if length(found_keys) > 0 do + {:ok, fetched_params} + else + {:error, @required_params.one_of} + end + end + + defp fetch_required_params(params, :maybe) do + case get_missing_required_params(params, :maybe) do + [] -> + keys_to_fetch = Map.values(@maybe_required_params) + {:ok, Map.take(params, keys_to_fetch)} + + missing_params -> + {:error, Enum.reverse(missing_params)} + end + end + + defp all_of_required_keys_found?(fetched_params) do + Enum.all?(@required_params.all_of, &Map.has_key?(fetched_params, &1)) + end + + defp get_missing_required_params(fetched_params, :all_of) do + fetched_keys = fetched_params |> Map.keys() |> MapSet.new() + + @required_params.all_of + |> MapSet.new() + |> MapSet.difference(fetched_keys) + |> MapSet.to_list() + end + + defp get_missing_required_params(fetched_params, :maybe) do + Enum.reduce(@maybe_required_params, [], fn {[key1, key2], expectation}, missing_params -> + has_key1? = Map.has_key?(fetched_params, key1) + has_key2? = Map.has_key?(fetched_params, key2) + has_expectation? = Map.has_key?(fetched_params, expectation) + + case {has_key1?, has_key2?, has_expectation?} do + {true, true, false} -> + [expectation | missing_params] + + _ -> + missing_params + end + end) + end + + defp to_block_number(params, param_key) do + case params[param_key] do + "latest" -> + Chain.max_consensus_block_number() + + _ -> + to_integer(params, param_key) + end + end + + defp to_integer(params, param_key) do + case Integer.parse(params[param_key]) do + {integer, ""} -> + {:ok, integer} + + _ -> + {:error, param_key} + end + end + + defp to_address_hash(nil), do: {:ok, nil} + + defp to_address_hash(address_hash_string) do + case Chain.string_to_address_hash(address_hash_string) do + :error -> + {:error, "address"} + + {:ok, address_hash} -> + {:ok, address_hash} + end + end + + defp validate_topic_operators(params) do + topic_operator_keys = Map.values(@maybe_required_params) + + first_invalid_topic_operator = + Enum.find(topic_operator_keys, fn topic_operator -> + params[topic_operator] not in ["and", "or", nil] + end) + + case first_invalid_topic_operator do + nil -> + :ok + + invalid_topic_operator -> + {:error, invalid_topic_operator} + end + end + + defp list_logs(filter) do + case Etherscan.list_logs(filter) do + [] -> {:error, :not_found} + logs -> {:ok, logs} + end + end +end 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 new file mode 100644 index 0000000..5e57d51 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex @@ -0,0 +1,127 @@ +defmodule BlockScoutWeb.API.RPC.RPCTranslator do + @moduledoc """ + Converts an RPC-style request into a controller action. + + Requests are expected to have URL query params like `?module=module&action=action`. + + ## Configuration + + The plugs needs a map relating a `module` string to a controller module. + + # In a router + forward "/api", RPCTranslator, %{"block" => BlockController} + + """ + + require Logger + + import Plug.Conn + import Phoenix.Controller, only: [put_view: 2] + + alias BlockScoutWeb.AccessHelpers + alias BlockScoutWeb.API.APILogger + alias BlockScoutWeb.API.RPC.RPCView + alias Phoenix.Controller + alias Plug.Conn + + def init(opts) do + opts + end + + def call(%Conn{params: %{"module" => module, "action" => action}} = conn, translations) do + with true <- valid_api_request_path(conn), + {:ok, {controller, write_actions}} <- translate_module(translations, module), + {:ok, action} <- translate_action(action), + true <- action_accessed?(action, write_actions), + :ok <- AccessHelpers.check_rate_limit(conn), + {:ok, conn} <- call_controller(conn, controller, action) do + APILogger.log(conn) + conn + else + {:error, :no_action} -> + conn + |> put_status(400) + |> put_view(RPCView) + |> Controller.render(:error, error: "Unknown action") + |> halt() + + {:error, error} -> + Logger.error(fn -> ["Error while calling RPC action", inspect(error)] end) + + conn + |> put_status(500) + |> put_view(RPCView) + |> Controller.render(:error, error: "Something went wrong.") + |> halt() + + :rate_limit_reached -> + AccessHelpers.handle_rate_limit_deny(conn) + + _ -> + conn + |> put_status(500) + |> put_view(RPCView) + |> Controller.render(:error, error: "Something went wrong.") + |> halt() + end + end + + def call(%Conn{} = conn, _) do + conn + |> put_status(400) + |> put_view(RPCView) + |> Controller.render(:error, error: "Params 'module' and 'action' are required parameters") + |> halt() + end + + @doc false + @spec translate_module(map(), String.t()) :: {:ok, {module(), list(atom())}} | {:error, :no_action} + defp translate_module(translations, module) do + module_lowercase = String.downcase(module) + + case Map.fetch(translations, module_lowercase) do + {:ok, module} -> {:ok, module} + _ -> {:error, :no_action} + end + end + + @doc false + @spec translate_action(String.t()) :: {:ok, atom()} | {:error, :no_action} + defp translate_action(action) do + action_lowercase = String.downcase(action) + {:ok, String.to_existing_atom(action_lowercase)} + rescue + ArgumentError -> {:error, :no_action} + end + + defp action_accessed?(action, write_actions) do + conf = Application.get_env(:block_scout_web, BlockScoutWeb.ApiRouter) + + if action in write_actions do + conf[:writing_enabled] || {:error, :no_action} + else + conf[:reading_enabled] || {:error, :no_action} + end + end + + @doc false + @spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | {:error, :no_action} | {:error, Exception.t()} + defp call_controller(conn, controller, action) do + if :erlang.function_exported(controller, action, 2) do + {:ok, controller.call(conn, action)} + else + {:error, :no_action} + end + rescue + e -> + {:error, Exception.format(:error, e, __STACKTRACE__)} + end + + defp valid_api_request_path(conn) do + if conn.request_path == "/api" || conn.request_path == "/api/v1" do + true + else + false + end + end +end 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 new file mode 100644 index 0000000..8c6d995 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex @@ -0,0 +1,90 @@ +defmodule BlockScoutWeb.API.RPC.StatsController do + use BlockScoutWeb, :controller + + use Explorer.Schema + + alias Explorer + alias Explorer.{Chain, Etherscan, ExchangeRates} + alias Explorer.Chain.Cache.{AddressSum, AddressSumMinusBurnt} + alias Explorer.Chain.Wei + + def tokensupply(conn, params) do + with {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params), + {:format, {:ok, address_hash}} <- to_address_hash(contractaddress_param), + {:token, {:ok, token}} <- {:token, Chain.token_from_address_hash(address_hash)} do + render(conn, "tokensupply.json", total_supply: Decimal.to_string(token.total_supply)) + else + {:contractaddress_param, :error} -> + render(conn, :error, error: "Query parameter contract address is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid contract address format") + + {:token, {:error, :not_found}} -> + render(conn, :error, error: "contract address not found") + end + end + + def ethsupplyexchange(conn, _params) do + wei_total_supply = + Chain.total_supply() + |> Decimal.new() + |> Wei.from(:ether) + |> Wei.to(:wei) + |> Decimal.to_string() + + render(conn, "ethsupplyexchange.json", total_supply: wei_total_supply) + end + + def ethsupply(conn, _params) do + cached_wei_total_supply = AddressSum.get_sum() + + render(conn, "ethsupply.json", total_supply: cached_wei_total_supply) + end + + def coinsupply(conn, _params) do + cached_coin_total_supply_wei = AddressSumMinusBurnt.get_sum_minus_burnt() + + coin_total_supply_wei = + if Decimal.compare(cached_coin_total_supply_wei, 0) == :gt do + cached_coin_total_supply_wei + else + Chain.get_last_fetched_counter("sum_coin_total_supply_minus_burnt") + end + + cached_coin_total_supply = + %Wei{value: Decimal.new(coin_total_supply_wei)} + |> Wei.to(:ether) + |> Decimal.to_string(:normal) + + render(conn, "coinsupply.json", total_supply: cached_coin_total_supply) + end + + def coinprice(conn, _params) do + symbol = Explorer.coin() + rates = ExchangeRates.lookup(symbol) + + render(conn, "coinprice.json", rates: rates) + end + + defp fetch_contractaddress(params) do + {:contractaddress_param, Map.fetch(params, "contractaddress")} + end + + defp to_address_hash(address_hash_string) do + {:format, Chain.string_to_address_hash(address_hash_string)} + end + + def totalfees(conn, params) do + case Map.fetch(params, "date") do + {:ok, date} -> + case Etherscan.get_total_fees_per_day(date) do + {:ok, total_fees} -> render(conn, "totalfees.json", total_fees: total_fees) + {:error, error} -> render(conn, :error, error: error) + end + + _ -> + render(conn, :error, error: "Required date input is missing.") + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex new file mode 100644 index 0000000..f2f8afb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex @@ -0,0 +1,60 @@ +defmodule BlockScoutWeb.API.RPC.TokenController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.RPC.Helpers + alias Explorer.{Chain, PagingOptions} + + def gettoken(conn, params) do + with {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params), + {:format, {:ok, address_hash}} <- to_address_hash(contractaddress_param), + {:token, {:ok, token}} <- {:token, Chain.token_from_address_hash(address_hash)} do + render(conn, "gettoken.json", %{token: token}) + else + {:contractaddress_param, :error} -> + render(conn, :error, error: "Query parameter contract address is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid contract address hash") + + {:token, {:error, :not_found}} -> + render(conn, :error, error: "contract address not found") + end + end + + def gettokenholders(conn, params) do + with pagination_options <- Helpers.put_pagination_options(%{}, params), + {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params), + {:format, {:ok, address_hash}} <- to_address_hash(contractaddress_param) do + options_with_defaults = + pagination_options + |> Map.put_new(:page_number, 0) + |> Map.put_new(:page_size, 10) + + options = [ + paging_options: %PagingOptions{ + key: nil, + page_number: options_with_defaults.page_number, + page_size: options_with_defaults.page_size + } + ] + + from_api = true + token_holders = Chain.fetch_token_holders_from_token_hash(address_hash, from_api, options) + render(conn, "gettokenholders.json", %{token_holders: token_holders}) + else + {:contractaddress_param, :error} -> + render(conn, :error, error: "Query parameter contract address is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid contract address hash") + end + end + + defp fetch_contractaddress(params) do + {:contractaddress_param, Map.fetch(params, "contractaddress")} + end + + defp to_address_hash(address_hash_string) do + {:format, Chain.string_to_address_hash(address_hash_string)} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex new file mode 100644 index 0000000..8cf1e55 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex @@ -0,0 +1,102 @@ +defmodule BlockScoutWeb.API.RPC.TransactionController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias Explorer.Chain + alias Explorer.Chain.Transaction + + def gettxinfo(conn, params) do + with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), + {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param), + {:transaction, {:ok, %Transaction{revert_reason: revert_reason, error: error} = transaction}} <- + transaction_from_hash(transaction_hash), + paging_options <- paging_options(params) do + from_api = true + logs = Chain.transaction_to_logs(transaction_hash, from_api, paging_options) + {logs, next_page} = split_list_by_page(logs) + + transaction_updated = + if (error == "Reverted" || error == "execution reverted") && !revert_reason do + %Transaction{transaction | revert_reason: Chain.fetch_tx_revert_reason(transaction)} + else + transaction + end + + render(conn, :gettxinfo, %{ + transaction: transaction_updated, + block_height: Chain.block_height(), + logs: logs, + next_page_params: next_page_params(next_page, logs, params) + }) + else + {:transaction, :error} -> + render(conn, :error, error: "Transaction not found") + + {:txhash_param, :error} -> + render(conn, :error, error: "Query parameter txhash is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid txhash format") + end + end + + def gettxreceiptstatus(conn, params) do + with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), + {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do + status = to_transaction_status(transaction_hash) + render(conn, :gettxreceiptstatus, %{status: status}) + else + {:txhash_param, :error} -> + render(conn, :error, error: "Query parameter txhash is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid txhash format") + end + end + + def getstatus(conn, params) do + with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), + {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do + error = to_transaction_error(transaction_hash) + render(conn, :getstatus, %{error: error}) + else + {:txhash_param, :error} -> + render(conn, :error, error: "Query parameter txhash is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid txhash format") + end + end + + defp fetch_txhash(params) do + {:txhash_param, Map.fetch(params, "txhash")} + end + + defp transaction_from_hash(transaction_hash) do + case Chain.hash_to_transaction(transaction_hash, necessity_by_association: %{block: :required}) do + {:error, :not_found} -> {:transaction, :error} + {:ok, transaction} -> {:transaction, {:ok, transaction}} + end + end + + defp to_transaction_hash(transaction_hash_string) do + {:format, Chain.string_to_transaction_hash(transaction_hash_string)} + end + + defp to_transaction_status(transaction_hash) do + case Chain.hash_to_transaction(transaction_hash) do + {:error, :not_found} -> "" + {:ok, transaction} -> transaction.status + end + end + + defp to_transaction_error(transaction_hash) do + with {:ok, transaction} <- Chain.hash_to_transaction(transaction_hash), + {:error, error} <- Chain.transaction_to_status(transaction) do + error + else + _ -> "" + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex new file mode 100644 index 0000000..653eb14 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex @@ -0,0 +1,69 @@ +defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.APILogger + alias Explorer.Chain + alias Explorer.Chain.Hash.Address + + def create(conn, params) do + APILogger.log(conn) + + if auth_token(conn) == actual_token() do + with {:ok, hash} <- validate_address_hash(params["address_hash"]), + :ok <- Chain.check_address_exists(hash), + {:contract, :not_found} <- + {:contract, Chain.check_decompiled_contract_exists(params["address_hash"], params["decompiler_version"])} do + case Chain.create_decompiled_smart_contract(params) do + {:ok, decompiled_smart_contract} -> + send_resp(conn, :created, encode(decompiled_smart_contract)) + + {:error, changeset} -> + errors = + changeset.errors + |> Enum.into(%{}, fn {field, {message, _}} -> + {field, message} + end) + + send_resp(conn, :unprocessable_entity, encode(errors)) + end + else + :invalid_address -> + send_resp(conn, :unprocessable_entity, encode(%{error: "address_hash is invalid"})) + + :not_found -> + send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) + + {:contract, :ok} -> + send_resp( + conn, + :unprocessable_entity, + encode(%{error: "decompiled code already exists for the decompiler version"}) + ) + end + else + send_resp(conn, :forbidden, "") + end + end + + defp validate_address_hash(address_hash) do + case Address.cast(address_hash) do + {:ok, hash} -> {:ok, hash} + :error -> :invalid_address + end + end + + defp auth_token(conn) do + case get_req_header(conn, "auth_token") do + [token] -> token + other -> other + end + end + + defp actual_token do + Application.get_env(:block_scout_web, :decompiled_smart_contract_token) + end + + defp encode(data) do + Jason.encode!(data) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex new file mode 100644 index 0000000..7b70b4b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex @@ -0,0 +1,48 @@ +defmodule BlockScoutWeb.API.V1.GasPriceOracleController do + use BlockScoutWeb, :controller + + alias Explorer.Chain.Cache.GasPriceOracle + + require Logger + + def gas_price_oracle(conn, _) do + case GasPriceOracle.get_gas_prices() do + {:ok, gas_prices} -> + send_with_content_type(conn, :ok, result(gas_prices)) + + nil -> + empty_gas_prices = %{ + "slow" => nil, + "average" => nil, + "fast" => nil + } + + send_with_content_type(conn, :internal_server_error, result(empty_gas_prices)) + + status -> + send_with_content_type(conn, :internal_server_error, error(status)) + end + end + + defp send_with_content_type(conn, status, result) do + conn + |> put_resp_content_type("application/json") + |> send_resp(status, result) + end + + def result(gas_prices) do + gas_prices + |> Jason.encode!() + end + + def error({:error, error}) do + Logger.error(fn -> ["Something went wrong while estimates gas prices in the gas price oracle: ", inspect(error)] end) + + %{ + "error_code" => 6001, + "error_title" => "Error", + "error_description" => "Internal server error" + } + |> Jason.encode!() + end +end 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 new file mode 100644 index 0000000..52352d5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex @@ -0,0 +1,64 @@ +defmodule BlockScoutWeb.API.V1.HealthController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.APILogger + alias Explorer.Chain + alias Timex.Duration + + def health(conn, _) do + APILogger.log(conn) + + with {:ok, number, timestamp} <- Chain.last_db_block_status(), + {:ok, cache_number, cache_timestamp} <- Chain.last_cache_block_status() do + send_resp(conn, :ok, result(number, timestamp, cache_number, cache_timestamp)) + else + status -> send_resp(conn, :internal_server_error, error(status)) + end + end + + def result(number, timestamp, cache_number, cache_timestamp) do + %{ + "healthy" => true, + "data" => %{ + "latest_block_number" => to_string(number), + "latest_block_inserted_at" => to_string(timestamp), + "cache_latest_block_number" => to_string(cache_number), + "cache_latest_block_inserted_at" => to_string(cache_timestamp) + } + } + |> Jason.encode!() + end + + def error({:error, :no_blocks}) do + %{ + "healthy" => false, + "error_code" => 5002, + "error_title" => "no blocks in db", + "error_description" => "There are no blocks in the DB" + } + |> Jason.encode!() + 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 #{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) + } + } + |> Jason.encode!() + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/supply_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/supply_controller.ex new file mode 100644 index 0000000..869843a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/supply_controller.ex @@ -0,0 +1,14 @@ +defmodule BlockScoutWeb.API.V1.SupplyController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.APILogger + alias Explorer.Chain + + def supply(conn, _) do + APILogger.log(conn) + total_supply = Chain.total_supply() + circulating_supply = Chain.circulating_supply() + + render(conn, :supply, total: total_supply, circulating: circulating_supply) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex new file mode 100644 index 0000000..6794b51 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex @@ -0,0 +1,65 @@ +defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.APILogger + alias Explorer.Chain + alias Explorer.Chain.Hash.Address + alias Explorer.SmartContract.Solidity.Publisher + + def create(conn, params) do + APILogger.log(conn) + + with {:ok, hash} <- validate_address_hash(params["address_hash"]), + :ok <- Chain.check_address_exists(hash), + {:contract, :not_found} <- {:contract, Chain.check_verified_smart_contract_exists(hash)} do + external_libraries = fetch_external_libraries(params) + + case Publisher.publish(hash, params, external_libraries) do + {:ok, _} -> + send_resp(conn, :created, encode(%{status: :success})) + + {:error, changeset} -> + errors = + changeset.errors + |> Enum.into(%{}, fn {field, {message, _}} -> + {field, message} + end) + + send_resp(conn, :unprocessable_entity, encode(errors)) + end + else + :invalid_address -> + send_resp(conn, :unprocessable_entity, encode(%{error: "address_hash is invalid"})) + + :not_found -> + send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) + + {:contract, :ok} -> + send_resp( + conn, + :unprocessable_entity, + encode(%{error: "verified code already exists for this address"}) + ) + end + end + + defp validate_address_hash(address_hash) do + case Address.cast(address_hash) do + {:ok, hash} -> {:ok, hash} + :error -> :invalid_address + end + end + + defp encode(data) do + Jason.encode!(data) + end + + defp fetch_external_libraries(params) do + 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 +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 0000000..717210d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -0,0 +1,238 @@ +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 + ] + + 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 + } + ] + + @transaction_with_tt_necessity_by_association [ + 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, + [token_transfers: :token] => :optional, + [token_transfers: :to_address] => :optional, + [token_transfers: :from_address] => :optional, + [token_transfers: :token_contract_address] => :optional, + :block => :required + } + ] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + def address(conn, %{"address_hash" => address_hash_string}) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash)} do + conn + |> put_status(200) + |> render(:address, %{address: address}) + end + end + + def token_balances(conn, %{"address_hash" => address_hash_string}) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)} 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)} 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_params(next_page, transactions, 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)} do + options = + @transaction_with_tt_necessity_by_association + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(current_filter(params)) + + results_plus_one = + Chain.address_hash_to_token_transfers( + address_hash, + options + ) + + {transactions, next_page} = split_list_by_page(results_plus_one) + + next_page_params = next_page_params(next_page, transactions, params) + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:transactions, %{transactions: transactions, 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)} 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_params(next_page, internal_transactions, 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)} 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_params(next_page, logs, 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)} 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_params(next_page, logs, 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)} 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_params(next_page, blocks, 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)} 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_params(next_page, coin_balances, 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}) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)} 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 0000000..24a3daf --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -0,0 +1,81 @@ +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: [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_params(next_page, blocks, 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_params(next_page, transactions, 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 0000000..9d76eee --- /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 0000000..20f9000 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -0,0 +1,38 @@ +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, _}) 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 +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 0000000..417eaf4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex @@ -0,0 +1,40 @@ +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: 5} + ) + + conn + |> put_status(200) + |> put_view(TransactionView) + |> render(:transactions, %{transactions: recent_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 0000000..59b06a3 --- /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 0000000..fa1a46a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -0,0 +1,104 @@ +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) + } + ) + 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/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex new file mode 100644 index 0000000..cce9bd7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -0,0 +1,221 @@ +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: [paging_options: 2, filter_options: 1, method_filter_options: 1, type_filter_options: 1] + + 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: :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}) 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 + )}, + preloaded <- Chain.preload_token_transfers(transaction, @token_transfers_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) + method_filter_options = method_filter_options(params) + type_filter_options = type_filter_options(params) + + full_options = + Keyword.merge( + [ + necessity_by_association: @transaction_necessity_by_association + ], + paging_options(params, filter_options) + ) + + transactions_plus_one = + Chain.recent_transactions(full_options, filter_options, method_filter_options, type_filter_options) + + {transactions, next_page} = split_list_by_page(transactions_plus_one) + + next_page_params = next_page_params(next_page, transactions, 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}) 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)} 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)} do + full_options = + Keyword.merge( + [ + necessity_by_association: @token_transfers_neccessity_by_association + ], + paging_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_params(next_page, token_transfers, 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)} 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_params(next_page, internal_transactions, 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)} 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_params(next_page, logs, 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/api_docs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex new file mode 100644 index 0000000..4a658c4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex @@ -0,0 +1,18 @@ +defmodule BlockScoutWeb.APIDocsController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Etherscan + alias Explorer.EthRPC + + def index(conn, _params) do + conn + |> assign(:documentation, Etherscan.get_documentation()) + |> render("index.html") + end + + def eth_rpc(conn, _params) do + conn + |> assign(:documentation, EthRPC.methods()) + |> render("eth_rpc.html") + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex new file mode 100644 index 0000000..ed117ef --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex @@ -0,0 +1,116 @@ +defmodule BlockScoutWeb.BlockController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias BlockScoutWeb.{BlockView, Controller} + alias Explorer.Chain + alias Phoenix.View + + def index(conn, params) do + case params["block_type"] do + "Uncle" -> + uncle(conn, params) + + "Reorg" -> + reorg(conn, params) + + _ -> + [ + necessity_by_association: %{ + :transactions => :optional, + [miner: :names] => :optional, + :rewards => :optional + }, + block_type: "Block" + ] + |> handle_render(conn, params) + end + end + + def show(conn, %{"hash_or_number" => hash_or_number}) do + block_transaction_path = + conn + |> block_transaction_path(:index, hash_or_number) + |> Controller.full_path() + + redirect(conn, to: block_transaction_path) + end + + def reorg(conn, params) do + [ + necessity_by_association: %{ + :transactions => :optional, + [miner: :names] => :optional, + :rewards => :optional + }, + block_type: "Reorg" + ] + |> handle_render(conn, params) + end + + def uncle(conn, params) do + [ + necessity_by_association: %{ + :transactions => :optional, + [miner: :names] => :optional, + :nephews => :required, + :rewards => :optional + }, + block_type: "Uncle" + ] + |> handle_render(conn, params) + end + + defp handle_render(full_options, conn, %{"type" => "JSON"} = params) do + blocks_plus_one = + full_options + |> Keyword.merge(paging_options(params)) + |> Chain.list_blocks() + + {blocks, next_page} = split_list_by_page(blocks_plus_one) + + block_type = Keyword.get(full_options, :block_type, "Block") + + next_page_path = + case next_page_params(next_page, blocks, params) do + nil -> + nil + + next_page_params -> + params_with_block_type = + next_page_params + |> Map.delete("type") + |> Map.put("block_type", block_type) + + blocks_path( + conn, + :index, + params_with_block_type + ) + end + + json( + conn, + %{ + items: + Enum.map(blocks, fn block -> + View.render_to_string( + BlockView, + "_tile.html", + block: block, + block_type: block_type + ) + end), + next_page_path: next_page_path + } + ) + end + + defp handle_render(full_options, conn, _params) do + render(conn, "index.html", + current_path: Controller.current_full_path(conn), + block_type: Keyword.get(full_options, :block_type, "Block") + ) + 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 new file mode 100644 index 0000000..facda14 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex @@ -0,0 +1,169 @@ +defmodule BlockScoutWeb.BlockTransactionController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [paging_options: 1, put_key_value_to_paging_options: 3, next_page_params: 3, split_list_by_page: 1] + + import Explorer.Chain, only: [hash_to_block: 2, number_to_block: 2, string_to_block_hash: 1] + + alias BlockScoutWeb.{Controller, TransactionView} + alias Explorer.Chain + alias Phoenix.View + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def index(conn, %{"block_hash_or_number" => formatted_block_hash_or_number, "type" => "JSON"} = params) do + case param_block_hash_or_number_to_block(formatted_block_hash_or_number, []) do + {:ok, block} -> + full_options = + Keyword.merge( + [ + necessity_by_association: %{ + :block => :optional, + [created_contract_address: :names] => :optional, + [from_address: :names] => :required, + [to_address: :names] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + } + ], + 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) + + {transactions, next_page} = split_list_by_page(transactions_plus_one) + + next_page_path = + case next_page_params(next_page, transactions, params) do + nil -> + nil + + next_page_params -> + block_transaction_path( + conn, + :index, + block, + Map.delete(next_page_params, "type") + ) + end + + items = + transactions + |> Enum.map(fn transaction -> + token_transfers_filtered_by_block_hash = + transaction.token_transfers + |> Enum.filter(fn token_transfer -> + token_transfer.block_hash == transaction.block_hash + end) + + transaction_with_transfers_filtered = + Map.put(transaction, :token_transfers, token_transfers_filtered_by_block_hash) + + View.render_to_string( + TransactionView, + "_tile.html", + transaction: transaction_with_transfers_filtered, + burn_address_hash: @burn_address_hash, + conn: conn + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_path + } + ) + + {:error, {:invalid, :hash}} -> + not_found(conn) + + {:error, {:invalid, :number}} -> + not_found(conn) + + {:error, :not_found} -> + conn + |> put_status(:not_found) + |> render( + "404.html", + block: nil, + block_above_tip: block_above_tip(formatted_block_hash_or_number) + ) + end + end + + def index(conn, %{"block_hash_or_number" => formatted_block_hash_or_number}) do + case param_block_hash_or_number_to_block(formatted_block_hash_or_number, + necessity_by_association: %{ + [miner: :names] => :required, + :uncles => :optional, + :nephews => :optional, + :rewards => :optional + } + ) do + {:ok, block} -> + block_transaction_count = Chain.block_to_transaction_count(block.hash) + + render( + conn, + "index.html", + block: block, + block_transaction_count: block_transaction_count, + current_path: Controller.current_full_path(conn) + ) + + {:error, {:invalid, :hash}} -> + not_found(conn) + + {:error, {:invalid, :number}} -> + not_found(conn) + + {:error, :not_found} -> + conn + |> put_status(:not_found) + |> render( + "404.html", + block: nil, + block_above_tip: block_above_tip(formatted_block_hash_or_number) + ) + end + end + + 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) + + :error -> + {:error, {:invalid, :hash}} + end + end + + 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) + + {:error, :invalid} -> + {:error, {:invalid, :number}} + end + end + + defp block_above_tip("0x" <> _), do: {:error, :hash} + + defp block_above_tip(block_hash_or_number) when is_binary(block_hash_or_number) do + case Chain.max_consensus_block_number() do + {:ok, max_consensus_block_number} -> + {block_number, _} = Integer.parse(block_hash_or_number) + {:ok, block_number > max_consensus_block_number} + + {:error, :not_found} -> + {:ok, true} + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex new file mode 100644 index 0000000..238f858 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex @@ -0,0 +1,53 @@ +defmodule BlockScoutWeb.Chain.MarketHistoryChartController do + use BlockScoutWeb, :controller + + alias Explorer.{Chain, Market} + alias Explorer.ExchangeRates.Token + + def show(conn, _params) do + if ajax?(conn) do + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + + recent_market_history = Market.fetch_recent_history() + + market_history_data = + case recent_market_history do + [today | the_rest] -> + encode_market_history_data([%{today | closing_price: exchange_rate.usd_value} | the_rest]) + + data -> + encode_market_history_data(data) + end + + json(conn, %{ + history_data: market_history_data, + supply_data: available_supply(Chain.supply_for_days(), exchange_rate) + }) + else + unprocessable_entity(conn) + end + end + + defp available_supply(:ok, exchange_rate) do + to_string(exchange_rate.available_supply || 0) + end + + defp available_supply({:ok, supply_for_days}, _exchange_rate) do + supply_for_days + |> Jason.encode() + |> case do + {:ok, data} -> data + _ -> [] + end + end + + defp encode_market_history_data(market_history_data) do + market_history_data + |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end) + |> Jason.encode() + |> case do + {:ok, data} -> data + _ -> [] + 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 new file mode 100644 index 0000000..dec6f4f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex @@ -0,0 +1,43 @@ +defmodule BlockScoutWeb.Chain.TransactionHistoryChartController do + use BlockScoutWeb, :controller + + alias Explorer.Chain.Transaction.History.TransactionStats + + def show(conn, _params) do + if ajax?(conn) do + [{:history_size, history_size}] = Application.get_env(:block_scout_web, __MODULE__, [{: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 + |> extract_history + |> encode_transaction_history_data + + json(conn, %{ + history_data: transaction_history_data + }) + else + unprocessable_entity(conn) + end + end + + defp extract_history(db_results) do + Enum.map(db_results, fn row -> + %{date: row.date, number_of_transactions: row.number_of_transactions} + end) + end + + defp encode_transaction_history_data(transaction_history_data) do + transaction_history_data + |> Jason.encode() + |> case do + {:ok, data} -> data + _ -> [] + end + end +end 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 new file mode 100644 index 0000000..61ac896 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -0,0 +1,178 @@ +defmodule BlockScoutWeb.ChainController do + use BlockScoutWeb, :controller + + 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} + alias Explorer.Chain.Cache.Block, as: BlockCache + alias Explorer.Chain.Cache.GasUsage + alias Explorer.Chain.Cache.Transaction, as: TransactionCache + alias Explorer.Chain.Supply.RSK + alias Explorer.Counters.AverageBlockTime + alias Explorer.ExchangeRates.Token + alias Explorer.Market + alias Phoenix.View + + def show(conn, _params) do + transaction_estimated_count = TransactionCache.estimated_count() + total_gas_usage = GasUsage.total() + block_count = BlockCache.estimated_count() + address_count = Chain.address_estimated_count() + + market_cap_calculation = + 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() + + chart_data_paths = %{ + market: market_history_chart_path(conn, :show), + transaction: transaction_history_chart_path(conn, :show) + } + + chart_config = Application.get_env(:block_scout_web, :chart_config, %{}) + + render( + conn, + "show.html", + address_count: address_count, + average_block_time: AverageBlockTime.average_block_time(), + exchange_rate: exchange_rate, + chart_config: chart_config, + chart_config_json: Jason.encode!(chart_config), + chart_data_paths: chart_data_paths, + market_cap_calculation: market_cap_calculation, + transaction_estimated_count: transaction_estimated_count, + total_gas_usage: total_gas_usage, + transactions_path: recent_transactions_path(conn, :index), + transaction_stats: transaction_stats, + block_count: block_count, + gas_price: Application.get_env(:block_scout_web, :gas_price) + ) + end + + def search(conn, %{"q" => ""}) do + show(conn, []) + end + + def search(conn, %{"q" => query}) do + query + |> String.trim() + |> BlockScoutWeb.Chain.from_param() + |> case do + {:ok, item} -> + redirect_search_results(conn, item) + + {:error, :not_found} -> + search_path = + conn + |> search_path(:search_results, q: query) + |> Controller.full_path() + + redirect(conn, to: search_path) + end + end + + def search(conn, _), do: not_found(conn) + + def token_autocomplete(conn, %{"q" => term} = params) when is_binary(term) do + [paging_options: paging_options] = paging_options(params) + offset = (max(paging_options.page_number, 1) - 1) * paging_options.page_size + + results = + paging_options + |> Chain.joint_search(offset, term) + + encoded_results = + results + |> Enum.map(fn item -> + tx_hash_bytes = Map.get(item, :tx_hash) + block_hash_bytes = Map.get(item, :block_hash) + + item = + if tx_hash_bytes do + item + |> Map.replace(:tx_hash, "0x" <> Base.encode16(tx_hash_bytes, case: :lower)) + else + item + end + + item = + if block_hash_bytes do + item + |> Map.replace(:block_hash, "0x" <> Base.encode16(block_hash_bytes, case: :lower)) + else + item + end + + item + end) + + json(conn, encoded_results) + end + + def token_autocomplete(conn, _) do + json(conn, "{}") + end + + def chain_blocks(conn, _params) do + if ajax?(conn) do + blocks = + [paging_options: %PagingOptions{page_size: 4}] + |> Chain.list_blocks() + |> Repo.preload([[miner: :names], :transactions, :rewards]) + |> Enum.map(fn block -> + %{ + chain_block_html: + View.render_to_string( + ChainView, + "_block.html", + block: block + ), + block_number: block.number + } + end) + + json(conn, %{blocks: blocks}) + else + unprocessable_entity(conn) + end + end + + defp redirect_search_results(conn, %Address{} = item) do + address_path = + conn + |> address_path(:show, item) + |> Controller.full_path() + + redirect(conn, to: address_path) + end + + defp redirect_search_results(conn, %Block{} = item) do + block_path = + conn + |> block_path(:show, item) + |> Controller.full_path() + + redirect(conn, to: block_path) + end + + defp redirect_search_results(conn, %Transaction{} = item) do + transaction_path = + conn + |> transaction_path(:show, item) + |> Controller.full_path() + + redirect(conn, to: transaction_path) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/common_components_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/common_components_controller.ex new file mode 100644 index 0000000..379bd15 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/common_components_controller.ex @@ -0,0 +1,12 @@ +defmodule BlockScoutWeb.CommonComponentsController do + use BlockScoutWeb, :controller + + def index(conn, params) do + [] + |> handle_render(conn, params) + end + + defp handle_render(_full_options, conn, _params) do + render(conn, "index.html") + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex new file mode 100644 index 0000000..8cab1d2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.CsvExportController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias Explorer.Chain + + def index(conn, %{"address" => address_hash_string, "type" => type} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_address_exists(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + true <- supported_export_type(type) do + render(conn, "index.html", address_hash_string: address_hash_string, type: type) + else + _ -> + not_found(conn) + end + end + + def index(conn, _params) do + not_found(conn) + end + + defp supported_export_type(type) do + Enum.member?(supported_types(), type) + end + + defp supported_types do + [ + "internal-transactions", + "transactions", + "token-transfers", + "logs" + ] + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/page_not_found_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/page_not_found_controller.ex new file mode 100644 index 0000000..fabcb3b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/page_not_found_controller.ex @@ -0,0 +1,9 @@ +defmodule BlockScoutWeb.PageNotFoundController do + use BlockScoutWeb, :controller + + def index(conn, _params) do + conn + |> put_status(:not_found) + |> render("index.html", token: nil) + 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 new file mode 100644 index 0000000..906fbc3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex @@ -0,0 +1,69 @@ +defmodule BlockScoutWeb.PendingTransactionController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias BlockScoutWeb.{Controller, TransactionView} + alias Explorer.Chain + alias Phoenix.View + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def index(conn, %{"type" => "JSON"} = params) do + full_options = + Keyword.merge( + [ + necessity_by_association: %{ + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [created_contract_address: :names] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + } + ], + paging_options(params) + ) + + {transactions, next_page} = get_pending_transactions_and_next_page(full_options) + + next_page_url = + case next_page_params(next_page, transactions, params) do + nil -> + nil + + next_page_params -> + pending_transaction_path( + conn, + :index, + Map.delete(next_page_params, "type") + ) + end + + json( + conn, + %{ + items: + Enum.map(transactions, fn transaction -> + View.render_to_string( + TransactionView, + "_tile.html", + transaction: transaction, + burn_address_hash: @burn_address_hash, + conn: conn + ) + end), + next_page_path: next_page_url + } + ) + end + + def index(conn, _params) do + render(conn, "index.html", current_path: Controller.current_full_path(conn)) + end + + defp get_pending_transactions_and_next_page(options) do + 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 new file mode 100644 index 0000000..d692d6d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex @@ -0,0 +1,45 @@ +defmodule BlockScoutWeb.RecentTransactionsController do + use BlockScoutWeb, :controller + + alias Explorer.{Chain, PagingOptions} + alias Explorer.Chain.Hash + alias Phoenix.View + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def index(conn, _params) do + if ajax?(conn) do + recent_transactions = + Chain.recent_collated_transactions(true, + 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: 5} + ) + + transactions = + Enum.map(recent_transactions, fn transaction -> + %{ + transaction_hash: Hash.to_string(transaction.hash), + transaction_html: + View.render_to_string(BlockScoutWeb.TransactionView, "_tile.html", + transaction: transaction, + burn_address_hash: @burn_address_hash, + conn: conn + ) + } + end) + + json(conn, %{transactions: transactions}) + else + unprocessable_entity(conn) + end + end +end 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 new file mode 100644 index 0000000..b2f639a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/search_controller.ex @@ -0,0 +1,77 @@ +defmodule BlockScoutWeb.SearchController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias BlockScoutWeb.{Controller, SearchView} + alias Explorer.Chain + alias Phoenix.View + + def search_results(conn, %{"q" => query, "type" => "JSON"} = 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_url = + case next_page_params(next_page, search_results, params) do + nil -> + nil + + next_page_params -> + search_path(conn, :search_results, Map.delete(next_page_params, "type")) + end + + items = + search_results + |> Enum.with_index(1) + |> Enum.map(fn {result, _index} -> + View.render_to_string( + SearchView, + "_tile.html", + result: result, + conn: conn, + query: query + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_url + } + ) + end + + def search_results(conn, %{"type" => "JSON"}) do + json( + conn, + %{ + items: [] + } + ) + end + + def search_results(conn, %{"q" => query}) do + render( + conn, + "results.html", + query: query, + current_path: Controller.current_full_path(conn) + ) + end + + def search_results(conn, %{}) do + render( + conn, + "results.html", + query: nil, + current_path: Controller.current_full_path(conn) + ) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex new file mode 100644 index 0000000..ab3819c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -0,0 +1,252 @@ +defmodule BlockScoutWeb.SmartContractController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AddressView + alias Explorer.Chain + 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} = 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) + |> Tuple.to_list() + |> List.first() || @burn_address + else + @burn_address + end + + functions = + if action == "write" do + if contract_type == "proxy" do + Writer.write_functions_proxy(implementation_address_hash_string) + else + Writer.write_functions(address_hash) + end + else + if contract_type == "proxy" do + Reader.read_only_functions_proxy(address_hash, implementation_address_hash_string) + else + Reader.read_only_functions(address_hash) + end + end + + read_functions_required_wallet = + if action == "read" do + if contract_type == "proxy" do + Reader.read_functions_required_wallet_proxy(implementation_address_hash_string) + else + Reader.read_functions_required_wallet(address_hash) + end + else + [] + end + + contract_abi = Poison.encode!(address.smart_contract.abi) + + implementation_abi = + if contract_type == "proxy" do + implementation_address_hash_string + |> Chain.get_implementation_abi() + |> Poison.encode!() + else + [] + end + + conn + |> put_status(200) + |> put_layout(false) + |> render( + "_functions.html", + read_functions_required_wallet: read_functions_required_wallet, + read_only_functions: functions, + address: address, + contract_abi: contract_abi, + implementation_address: implementation_address_hash_string, + implementation_abi: implementation_abi, + contract_type: contract_type, + action: action + ) + else + {:custom_abi, true} -> + custom_abi_render(conn, params) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + + _ -> + not_found(conn) + end + end + + 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: %{ + :contracts_creation_internal_transaction => :optional, + :names => :optional, + :smart_contract => :optional, + :token => :optional, + :contracts_creation_transaction => :optional + } + ] + + 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 = + 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 custom_abi do + Reader.query_function_with_names_custom_abi( + address_hash, + %{method_id: params["method_id"], args: args}, + params["from"], + custom_abi.abi + ) + else + Reader.query_function_with_names( + address_hash, + %{method_id: params["method_id"], args: args}, + contract_type, + params["from"] + ) + end + + conn + |> put_status(200) + |> put_layout(false) + |> render( + "_function_response.html", + function_name: params["function_name"], + method_id: params["method_id"], + outputs: outputs, + names: names, + smart_contract_address: address_hash + ) + else + :error -> + unprocessable_entity(conn) + + :not_found -> + not_found(conn) + + _ -> + not_found(conn) + end + end + + defp convert_map_to_array(map) do + if is_turned_out_array?(map) do + map |> Map.values() |> try_to_map_elements() + else + try_to_map_elements(map) + end + end + + defp try_to_map_elements(values) do + if Enumerable.impl_for(values) do + Enum.map(values, &convert_map_to_array/1) + else + values + end + end + + defp is_turned_out_array?(map) when is_map(map), do: Enum.all?(Map.keys(map), &is_integer?/1) + + defp is_turned_out_array?(_), do: false + + defp is_integer?(string) when is_binary(string) do + case string |> String.trim() |> Integer.parse() do + {_, ""} -> + true + + _ -> + false + end + end + + defp is_integer?(integer) when is_integer(integer), do: true + + defp is_integer?(_), do: false +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 new file mode 100644 index 0000000..9a6bee2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex @@ -0,0 +1,56 @@ +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 + + def index(conn, %{"token_id" => address_hash_string} = params) do + options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] + + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_verified_smart_contract_exists(address_hash), + {:ok, token} <- Chain.token_from_address_hash(address_hash, options), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + %{type: type, action: action} = + cond do + TabHelpers.tab_active?("read-contract", conn.request_path) -> + %{type: :regular, action: :read} + + TabHelpers.tab_active?("write-contract", conn.request_path) -> + %{type: :regular, action: :write} + + TabHelpers.tab_active?("read-proxy", conn.request_path) -> + %{type: :proxy, action: :read} + + TabHelpers.tab_active?("write-proxy", conn.request_path) -> + %{type: :proxy, action: :write} + end + + render( + conn, + "index.html", + type: type, + action: action, + token: Market.add_price(token), + counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :not_found -> + not_found(conn) + + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..710ff74 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex @@ -0,0 +1,86 @@ +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} + alias Explorer.Chain.Address + alias Phoenix.View + + import BlockScoutWeb.Chain, + only: [ + split_list_by_page: 1, + paging_options: 1, + next_page_params: 3 + ] + + def index(conn, %{"token_id" => address_hash_string, "type" => "JSON"} = params) do + from_api = false + + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token} <- Chain.token_from_address_hash(address_hash), + token_balances <- Chain.fetch_token_holders_from_token_hash(address_hash, from_api, paging_options(params)), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + {token_balances_paginated, next_page} = split_list_by_page(token_balances) + + next_page_path = + case next_page_params(next_page, token_balances_paginated, params) do + nil -> + nil + + next_page_params -> + token_holder_path(conn, :index, address_hash, Map.delete(next_page_params, "type")) + end + + token_balances_json = + Enum.map(token_balances_paginated, fn token_balance -> + View.render_to_string(HolderView, "_token_balances.html", + address_hash: address_hash, + token_balance: token_balance, + token: token, + conn: conn + ) + end) + + json(conn, %{items: token_balances_json, next_page_path: next_page_path}) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index(conn, %{"token_id" => address_hash_string} = params) do + options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] + + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token} <- Chain.token_from_address_hash(address_hash, options), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "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)}), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..3ee291f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex @@ -0,0 +1,78 @@ +defmodule BlockScoutWeb.Tokens.Instance.HolderController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias BlockScoutWeb.Tokens.HolderView + alias Explorer.{Chain, Market} + alias Explorer.Chain.Address + alias Phoenix.View + + import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] + + def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id, "type" => "JSON"} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(token_address_hash), + {:ok, token} <- Chain.token_from_address_hash(address_hash), + token_holders <- + Chain.fetch_token_holders_from_token_hash_and_token_id(address_hash, token_id, paging_options(params)) do + {token_holders_paginated, next_page} = split_list_by_page(token_holders) + + next_page_path = + case next_page_params(next_page, token_holders_paginated, params) do + nil -> + nil + + next_page_params -> + token_instance_holder_path( + conn, + :index, + Address.checksum(token.contract_address_hash), + token_id, + Map.delete(next_page_params, "type") + ) + end + + holders_json = + token_holders_paginated + |> Enum.sort_by(& &1.value, &>=/2) + |> Enum.map(fn current_token_balance -> + View.render_to_string( + HolderView, + "_token_balances.html", + address_hash: address_hash, + token_balance: current_token_balance, + token: token + ) + end) + + json(conn, %{items: holders_json, next_page_path: next_page_path}) + else + _ -> + not_found(conn) + end + end + + def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id}) do + options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] + + with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), + {:ok, token} <- Chain.token_from_address_hash(hash, options), + {: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: %{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) + ) + else + _ -> + not_found(conn) + end + end + + def index(conn, _) do + not_found(conn) + end +end 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 new file mode 100644 index 0000000..b404cc9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.Tokens.Instance.MetadataController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias Explorer.{Chain, Market} + + def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id}) do + options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] + + with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), + {:ok, token} <- Chain.token_from_address_hash(hash, options), + {: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: %{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) + ) + else + not_found(conn) + end + else + _ -> + not_found(conn) + end + end + + def index(conn, _) do + not_found(conn) + end +end 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 new file mode 100644 index 0000000..1e92527 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex @@ -0,0 +1,80 @@ +defmodule BlockScoutWeb.Tokens.Instance.TransferController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias BlockScoutWeb.Tokens.TransferView + alias Explorer.{Chain, Market} + alias Explorer.Chain.Address + alias Phoenix.View + + import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id, "type" => "JSON"} = params) do + with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), + {:ok, token} <- Chain.token_from_address_hash(hash), + token_transfers <- + Chain.fetch_token_transfers_from_token_hash_and_token_id(hash, token_id, paging_options(params)) do + {token_transfers_paginated, next_page} = split_list_by_page(token_transfers) + + next_page_path = + case next_page_params(next_page, token_transfers_paginated, params) do + nil -> + nil + + next_page_params -> + token_instance_transfer_path( + conn, + :index, + Address.checksum(token.contract_address_hash), + token_id, + Map.delete(next_page_params, "type") + ) + end + + transfers_json = + Enum.map(token_transfers_paginated, fn transfer -> + View.render_to_string( + TransferView, + "_token_transfer.html", + conn: conn, + token: token, + token_transfer: transfer, + burn_address_hash: @burn_address_hash + ) + end) + + json(conn, %{items: transfers_json, next_page_path: next_page_path}) + else + _ -> + not_found(conn) + end + end + + def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id}) do + options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] + + with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), + {:ok, token} <- Chain.token_from_address_hash(hash, options), + {: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: %{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) + ) + else + _ -> + not_found(conn) + end + end + + def index(conn, _) do + not_found(conn) + end +end 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 new file mode 100644 index 0000000..d7d0a4d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex @@ -0,0 +1,26 @@ +defmodule BlockScoutWeb.Tokens.InstanceController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.Controller + alias Explorer.Chain + + 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_or_erc1155_token_instance_exists(token_id, hash) do + token_instance_transfer_path = + conn + |> token_instance_transfer_path(:index, token_address_hash, token_id) + |> Controller.full_path() + + redirect(conn, to: token_instance_transfer_path) + else + _ -> + not_found(conn) + end + end + + def show(conn, _) do + not_found(conn) + end +end 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 new file mode 100644 index 0000000..5129856 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex @@ -0,0 +1,87 @@ +defmodule BlockScoutWeb.Tokens.InventoryController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias BlockScoutWeb.Tokens.{HolderController, InventoryView} + alias Explorer.Chain + alias Explorer.Chain.TokenTransfer + alias Phoenix.View + + import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0] + + def index(conn, %{"token_id" => address_hash_string, "type" => "JSON"} = params) 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 = + 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) + + next_page_path = + case unique_tokens_next_page(next_page, unique_tokens_paginated, params) do + nil -> + nil + + next_page_params -> + token_inventory_path( + conn, + :index, + address_hash_string, + Map.delete(next_page_params, "type") + ) + end + + items = + unique_tokens_paginated + |> Enum.map(fn token_transfer -> + View.render_to_string( + InventoryView, + "_token.html", + token_transfer: token_transfer, + token: token, + conn: conn + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_path + } + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index(conn, params) do + HolderController.index(conn, params) + end + + defp unique_tokens_paging_options(%{"unique_token" => token_id}), + do: [paging_options: %{default_paging_options() | key: {token_id}}] + + defp unique_tokens_paging_options(_params), do: [paging_options: default_paging_options()] + + defp unique_tokens_next_page([], _list, _params), do: nil + + defp unique_tokens_next_page(_, list, params) do + Map.merge(params, paging_params(List.last(list))) + end + + defp paging_params(%TokenTransfer{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 new file mode 100644 index 0000000..6a1ea07 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/token_controller.ex @@ -0,0 +1,55 @@ +defmodule BlockScoutWeb.Tokens.TokenController do + use BlockScoutWeb, :controller + + require Logger + + 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)) + end + + 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) + + json(conn, %{transfer_count: transfer_count, token_holder_count: token_holder_count}) + + _ -> + 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/tokens_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex new file mode 100644 index 0000000..4e4f289 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex @@ -0,0 +1,74 @@ +defmodule BlockScoutWeb.TokensController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias BlockScoutWeb.{Controller, TokensView} + alias Explorer.Chain + alias Phoenix.View + + def index(conn, %{"type" => "JSON"} = params) do + filter = + if Map.has_key?(params, "filter") do + Map.get(params, "filter") + else + nil + end + + paging_params = + params + |> paging_options() + + tokens = Chain.list_top_tokens(filter, paging_params) + + {tokens_page, next_page} = split_list_by_page(tokens) + + next_page_path = + case next_page_params(next_page, tokens_page, params) do + nil -> + nil + + next_page_params -> + tokens_path( + conn, + :index, + Map.delete(next_page_params, "type") + ) + end + + items_count_str = Map.get(params, "items_count") + + items_count = + if items_count_str do + {items_count, _} = Integer.parse(items_count_str) + items_count + else + 0 + end + + items = + tokens_page + |> Enum.with_index(1) + |> Enum.map(fn {token, index} -> + View.render_to_string( + TokensView, + "_tile.html", + token: token, + index: items_count + index, + conn: conn + ) + 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)) + 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 new file mode 100644 index 0000000..ad7db93 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex @@ -0,0 +1,91 @@ +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} + alias Explorer.Chain.Address + alias Indexer.Fetcher.TokenTotalSupplyOnDemand + alias Phoenix.View + + import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + def index(conn, %{"token_id" => address_hash_string, "type" => "JSON"} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token} <- Chain.token_from_address_hash(address_hash), + token_transfers <- Chain.fetch_token_transfers_from_token_hash(address_hash, paging_options(params)), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + {token_transfers_paginated, next_page} = split_list_by_page(token_transfers) + + next_page_path = + case next_page_params(next_page, token_transfers_paginated, params) do + nil -> + nil + + next_page_params -> + token_transfer_path( + conn, + :index, + Address.checksum(token.contract_address_hash), + Map.delete(next_page_params, "type") + ) + end + + transfers_json = + Enum.map(token_transfers_paginated, fn transfer -> + View.render_to_string( + TransferView, + "_token_transfer.html", + conn: conn, + token: token, + token_transfer: transfer, + burn_address_hash: @burn_address_hash + ) + end) + + json(conn, %{items: transfers_json, next_page_path: next_page_path}) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index(conn, %{"token_id" => address_hash_string} = params) do + options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] + + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token} <- Chain.token_from_address_hash(address_hash, options), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do + render( + conn, + "index.html", + 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), + tags: get_address_tags(address_hash, current_user(conn)) + ) + else + {:restricted_access, _} -> + not_found(conn) + + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end +end 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 new file mode 100644 index 0000000..858b7dc --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex @@ -0,0 +1,252 @@ +defmodule BlockScoutWeb.TransactionController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + + import BlockScoutWeb.Chain, + only: [ + fetch_page_number: 1, + paging_options: 1, + next_page_params: 3, + update_page_parameters: 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, + TransactionInternalTransactionController, + TransactionTokenTransferController, + TransactionView + } + + alias Explorer.{Chain, Market} + alias Explorer.Chain.Cache.Transaction, as: TransactionCache + alias Explorer.ExchangeRates.Token + alias Phoenix.View + + @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, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + @default_options [ + 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 + } + ] + + def index(conn, %{"type" => "JSON"} = params) do + options = + @default_options + |> Keyword.merge(paging_options(params)) + + full_options = + options + |> Keyword.put( + :paging_options, + params + |> fetch_page_number() + |> update_page_parameters(Chain.default_page_size(), Keyword.get(options, :paging_options)) + ) + + %{total_transactions_count: transactions_count, transactions: transactions_plus_one} = + Chain.recent_collated_transactions_for_rap(full_options) + + {transactions, next_page} = + if fetch_page_number(params) == 1 do + split_list_by_page(transactions_plus_one) + else + {transactions_plus_one, nil} + end + + next_page_params = + if fetch_page_number(params) == 1 do + page_size = Chain.default_page_size() + + pages_limit = transactions_count |> Kernel./(page_size) |> Float.ceil() |> trunc() + + case next_page_params(next_page, transactions, params) do + nil -> + nil + + next_page_params -> + next_page_params + |> Map.delete("type") + |> Map.delete("items_count") + |> Map.put("pages_limit", pages_limit) + |> Map.put("page_size", page_size) + |> Map.put("page_number", 1) + end + else + Map.delete(params, "type") + end + + json( + conn, + %{ + items: + Enum.map(transactions, fn transaction -> + View.render_to_string( + TransactionView, + "_tile.html", + transaction: transaction, + burn_address_hash: @burn_address_hash, + conn: conn + ) + end), + next_page_params: next_page_params + } + ) + end + + def index(conn, _params) do + transaction_estimated_count = TransactionCache.estimated_count() + + render( + conn, + "index.html", + current_path: Controller.current_full_path(conn), + transaction_estimated_count: transaction_estimated_count + ) + end + + def show(conn, %{"id" => transaction_hash_string, "type" => "JSON"}) do + case Chain.string_to_transaction_hash(transaction_hash_string) do + {:ok, transaction_hash} -> + if Chain.transaction_has_token_transfers?(transaction_hash) do + TransactionTokenTransferController.index(conn, %{ + "transaction_id" => transaction_hash_string, + "type" => "JSON" + }) + else + TransactionInternalTransactionController.index(conn, %{ + "transaction_id" => transaction_hash_string, + "type" => "JSON" + }) + end + + :error -> + set_not_found_view(conn, transaction_hash_string) + end + end + + def show(conn, %{"id" => id} = params) do + with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(id), + :ok <- Chain.check_transaction_exists(transaction_hash) do + if Chain.transaction_has_token_transfers?(transaction_hash) do + with {:ok, transaction} <- + Chain.hash_to_transaction( + transaction_hash, + necessity_by_association: @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) do + render( + conn, + "show_token_transfers.html", + 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, + 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) + + {:error, :not_found} -> + set_not_found_view(conn, id) + + {:restricted_access, _} -> + set_not_found_view(conn, id) + end + else + with {:ok, transaction} <- + Chain.hash_to_transaction( + transaction_hash, + necessity_by_association: @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) do + render( + conn, + "show_internal_transactions.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, + 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) + + {:error, :not_found} -> + set_not_found_view(conn, id) + + {:restricted_access, _} -> + set_not_found_view(conn, id) + end + end + else + :error -> + set_invalid_view(conn, id) + + :not_found -> + set_not_found_view(conn, id) + end + end + + def set_not_found_view(conn, transaction_hash_string) do + conn + |> put_status(404) + |> 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 new file mode 100644 index 0000000..5cc1af7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -0,0 +1,130 @@ +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} + alias Explorer.ExchangeRates.Token + alias Phoenix.View + + 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, []), + {: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: %{ + [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 + } + ], + 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_path = + case next_page_params(next_page, internal_transactions, params) do + nil -> + nil + + next_page_params -> + transaction_internal_transaction_path( + conn, + :index, + transaction_hash, + Map.delete(next_page_params, "type") + ) + end + + items = + internal_transactions + |> Enum.map(fn internal_transaction -> + View.render_to_string( + InternalTransactionView, + "_tile.html", + internal_transaction: internal_transaction + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_path + } + ) + else + {:restricted_access, _} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + :error -> + TransactionController.set_invalid_view(conn, transaction_hash_string) + + {: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(), + 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, + 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) + + {:error, :not_found} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + end + end +end 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 new file mode 100644 index 0000000..bb29f78 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex @@ -0,0 +1,123 @@ +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} + alias Explorer.ExchangeRates.Token + alias Phoenix.View + + 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, transaction} <- + Chain.hash_to_transaction(transaction_hash, + necessity_by_association: %{[to_address: :smart_contract] => :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 = + Keyword.merge( + [ + necessity_by_association: %{ + [address: :names] => :optional, + [address: :smart_contract] => :optional, + address: :optional + } + ], + paging_options(params) + ) + + from_api = false + 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_url = + case next_page_params(next_page, logs, params) do + nil -> + nil + + next_page_params -> + transaction_log_path(conn, :index, transaction, Map.delete(next_page_params, "type")) + end + + items = + logs + |> Enum.map(fn log -> + View.render_to_string( + TransactionLogView, + "_logs.html", + log: log, + conn: conn, + transaction: transaction + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_url + } + ) + else + {:restricted_access, _} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + :error -> + TransactionController.set_invalid_view(conn, transaction_hash_string) + + {:error, :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] => :required, + [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", + 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(), + 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) + + {:error, :not_found} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + end + end +end 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 new file mode 100644 index 0000000..49447c6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex @@ -0,0 +1,111 @@ +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} + alias Explorer.Chain.Import + alias Explorer.Chain.Import.Runner.InternalTransactions + alias Explorer.ExchangeRates.Token + + def index(conn, %{"transaction_id" => hash_string} = params) do + with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), + {:ok, transaction} <- + Chain.hash_to_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 + if is_nil(transaction.block_number) do + render_raw_trace(conn, [], transaction, hash) + else + internal_transactions = Chain.all_transaction_to_internal_transactions(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: 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(hash) + + {:error, _} -> + internal_transactions + + :ignore -> + internal_transactions + end + end + + render_raw_trace(conn, internal_transactions, transaction, hash) + end + else + {:restricted_access, _} -> + TransactionController.set_not_found_view(conn, hash_string) + + :error -> + TransactionController.set_invalid_view(conn, hash_string) + + {:error, :not_found} -> + TransactionController.set_not_found_view(conn, hash_string) + end + end + + defp render_raw_trace(conn, internal_transactions, transaction, hash) do + render( + conn, + "index.html", + 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, + 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 0000000..14b07de --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex @@ -0,0 +1,464 @@ +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, + token: :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 = transaction.from_address + from_after = do_update_coin_balance_from_tx(from_hash, transaction, from_before, block) + + from_coin_entry = + if from_hash not in [to_hash, miner_hash] do + 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 = transaction.to_address + to_after = do_update_coin_balance_from_tx(to_hash, transaction, to_before, block) + + to_coin_entry = + if to_hash != miner_hash do + 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: Enum.sort([from_coin_entry, to_coin_entry, miner_entry | items])}) + else + {:restricted_access, _} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + :error -> + TransactionController.set_invalid_view(conn, transaction_hash_string) + + {: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 -> + TransactionController.set_invalid_view(conn, transaction_hash_string) + + {: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 new file mode 100644 index 0000000..931df3c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex @@ -0,0 +1,137 @@ +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} + alias Explorer.ExchangeRates.Token + alias Phoenix.View + + {: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, + [] + ), + {: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: %{ + [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 + } + ], + paging_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_url = + case next_page_params(next_page, token_transfers, params) do + nil -> + nil + + next_page_params -> + transaction_token_transfer_path(conn, :index, transaction_hash, Map.delete(next_page_params, "type")) + end + + items = + token_transfers + |> Enum.map(fn transfer -> + View.render_to_string( + TransactionTokenTransferView, + "_token_transfer.html", + token_transfer: transfer, + burn_address_hash: @burn_address_hash, + conn: conn + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_url + } + ) + else + {:restricted_access, _} -> + TransactionController.set_not_found_view(conn, transaction_hash_string) + + :error -> + TransactionController.set_invalid_view(conn, transaction_hash_string) + + {: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), + current_user: current_user(conn), + show_token_transfers: true, + 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) + + {: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 +end 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 0000000..c75b25e --- /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/counters/blocks_indexed_counter.ex b/apps/block_scout_web/lib/block_scout_web/counters/blocks_indexed_counter.ex new file mode 100644 index 0000000..8c4ed18 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/counters/blocks_indexed_counter.ex @@ -0,0 +1,58 @@ +defmodule BlockScoutWeb.Counters.BlocksIndexedCounter do + @moduledoc """ + Module responsible for fetching and consolidating the number blocks 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 blocks 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_blocks_indexed/0) + + schedule_next_consolidation() + end + + {:ok, args} + end + + def calculate_blocks_indexed do + indexed_ratio_blocks = Chain.indexed_ratio_blocks() + + finished? = Chain.finished_indexing?(indexed_ratio_blocks) + + Notifier.broadcast_blocks_indexed_ratio(indexed_ratio_blocks, finished?) + end + + defp schedule_next_consolidation do + Process.send_after(self(), :calculate_blocks_indexed, :timer.minutes(5)) + end + + @impl true + def handle_info(:calculate_blocks_indexed, state) do + calculate_blocks_indexed() + + schedule_next_consolidation() + + {:noreply, state} + end +end 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 0000000..76085fe --- /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/csp_header.ex b/apps/block_scout_web/lib/block_scout_web/csp_header.ex new file mode 100644 index 0000000..cb81c9b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/csp_header.ex @@ -0,0 +1,30 @@ +defmodule BlockScoutWeb.CSPHeader do + @moduledoc """ + Plug to set content-security-policy with websocket endpoints + """ + + alias Phoenix.Controller + alias Plug.Conn + + def init(opts), do: opts + + def call(conn, _opts) do + Controller.put_secure_browser_headers(conn, %{ + "content-security-policy" => "\ + connect-src 'self' #{websocket_endpoints(conn)} wss://*.bridge.walletconnect.org/ https://request-global.czilladx.com/ https://raw.githubusercontent.com/trustwallet/assets/ https://registry.walletconnect.org/data/wallets.json https://*.poa.network;\ + default-src 'self';\ + script-src 'self' 'unsafe-inline' 'unsafe-eval' https://coinzillatag.com https://www.google.com https://www.gstatic.com;\ + style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\ + img-src 'self' * data:;\ + media-src 'self' * data:;\ + font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\ + frame-src 'self' 'unsafe-inline' 'unsafe-eval' https://request-global.czilladx.com/ https://www.google.com;\ + " + }) + end + + defp websocket_endpoints(conn) do + host = Conn.get_req_header(conn, "host") + "ws://#{host} wss://#{host}" + 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 new file mode 100644 index 0000000..19c6163 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/endpoint.ex @@ -0,0 +1,90 @@ +defmodule BlockScoutWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :block_scout_web + use Absinthe.Phoenix.Endpoint + + 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. + # + # You should set gzip to true if you are running phoenix.digest + # when deploying your static files in production. + plug( + Plug.Static, + at: "/", + from: :block_scout_web, + gzip: true, + only: ~w( + css + fonts + images + js + android-chrome-192x192.png + android-chrome-512x512.png + apple-touch-icon.png + browserconfig.xml + mstile-150x150.png + safari-pinned-tab.svg + robots.txt + ), + only_matching: ~w(manifest) + ) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket) + plug(Phoenix.LiveReloader) + plug(Phoenix.CodeReloader) + end + + plug(Plug.RequestId) + plug(Plug.Logger) + + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 20_000_000, + query_string_length: 1_000_000, + pass: ["*/*"], + json_decoder: Poison + ) + + plug(Plug.MethodOverride) + plug(Plug.Head) + + # 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: BlockScoutWeb.Plug.RedisCookie, + key: "_explorer_key", + signing_salt: "iC2ksJHS", + same_site: "Lax", + http_only: false + ) + + use SpandexPhoenix + + plug(BlockScoutWeb.Prometheus.Exporter) + + # 'x-apollo-tracing' header for https://www.graphqlbin.com to work with our GraphQL endpoint + plug(CORSPlug, headers: ["x-apollo-tracing" | CORSPlug.defaults()[:headers]]) + + plug(BlockScoutWeb.Router) + + def init(_key, config) do + if config[:load_from_system_env] do + port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" + {:ok, Keyword.put(config, :http, [:inet6, port: port])} + else + {:ok, config} + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex new file mode 100644 index 0000000..6afc220 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -0,0 +1,3011 @@ +defmodule BlockScoutWeb.Etherscan do + @moduledoc """ + Documentation data for Etherscan-compatible API. + """ + + @account_balance_example_value %{ + "status" => "1", + "message" => "OK", + "result" => "663046792267785498951364" + } + + @account_balance_example_value_error %{ + "status" => "0", + "message" => "Invalid address hash", + "result" => nil + } + + @account_balancemulti_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "account" => "0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a", + "balance" => "40807168566070000000000", + "stale" => true + }, + %{ + "account" => "0x63a9975ba31b0b9626b34300f7f627147df1f526", + "balance" => "332567136222827062478", + "stale" => false + }, + %{ + "account" => "0x198ef1ec325a96cc354c7266a038be8b5c558f67", + "balance" => "185178830000000000", + "stale" => false + } + ] + } + + @account_pendingtxlist_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "hash" => "0x98beb27135aa0a25650557005ad962919d6a278c4b3dde7f4f6a3a1e65aa746c", + "nonce" => "0", + "from" => "0x3fb1cd2cd96c6d5c0b5eb3322d807b34482481d4", + "to" => "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", + "value" => "0", + "gas" => "122261", + "gasPrice" => "50000000000", + "input" => + "0xf00d4b5d000000000000000000000000036c8cecce8d8bbf0831d840d7f29c9e3ddefa63000000000000000000000000c5a96db085dda36ffbe390f455315d30d6d3dc52", + "contractAddress" => "", + "cumulativeGasUsed" => "122207", + "gasUsed" => "122207" + } + ] + } + + @account_txlist_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "blockNumber" => "65204", + "timeStamp" => "1439232889", + "hash" => "0x98beb27135aa0a25650557005ad962919d6a278c4b3dde7f4f6a3a1e65aa746c", + "nonce" => "0", + "blockHash" => "0x373d339e45a701447367d7b9c7cef84aab79c2b2714271b908cda0ab3ad0849b", + "transactionIndex" => "0", + "from" => "0x3fb1cd2cd96c6d5c0b5eb3322d807b34482481d4", + "to" => "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", + "value" => "0", + "gas" => "122261", + "gasPrice" => "50000000000", + "isError" => "0", + "txreceipt_status" => "1", + "input" => + "0xf00d4b5d000000000000000000000000036c8cecce8d8bbf0831d840d7f29c9e3ddefa63000000000000000000000000c5a96db085dda36ffbe390f455315d30d6d3dc52", + "contractAddress" => "", + "cumulativeGasUsed" => "122207", + "gasUsed" => "122207", + "confirmations" => "5994246" + } + ] + } + + @account_txlist_example_value_error %{ + "status" => "0", + "message" => "No transactions found", + "result" => [] + } + + @account_txlistinternal_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "blockNumber" => "6153702", + "timeStamp" => "1534362606", + "from" => "0x2ca1e3f250f56f1761b9a52bc42db53986085eff", + "to" => "", + "value" => "5488334153118633", + "contractAddress" => "0x883103875d905c11f9ac7dacbfc16deb39655361", + "transactionHash" => "0xd65b788c610949704a5f9aac2228c7c777434dfe11c863a12306f57fcbd8cdbb", + "index" => "0", + "input" => "", + "type" => "call", + "callType" => "delegatecall", + "gas" => "814937", + "gasUsed" => "536262", + "isError" => "0", + "errCode" => "" + } + ] + } + + @account_txlistinternal_example_value_error %{ + "status" => "0", + "message" => "No internal transactions found", + "result" => [] + } + + @account_eth_get_balance_example_value %{ + "jsonrpc" => "2.0", + "result" => "0x0234c8a3397aab58", + "id" => 1 + } + + @account_tokentx_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "blockNumber" => "5997843", + "timeStamp" => "1532086946", + "hash" => "0xd65b788c610949704a5f9aac2228c7c777434dfe11c863a12306f57fcbd8cdbb", + "nonce" => "765", + "blockHash" => "0x6169c5dc05d0051564ba3eae8ebfbdefda640c5f5ffc095846b8aed0b44f64ea", + "from" => "0x4e83362442b8d1bec281594cea3050c8eb01311c", + "contractAddress" => "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", + "logIndex" => "0", + "to" => "0x21e21ba085289f81a86921de890eed30f1ad2375", + "value" => "10000000000000000000", + "tokenName" => "Maker", + "tokenSymbol" => "MKR", + "tokenDecimal" => "18", + "transactionIndex" => "27", + "gas" => "44758", + "gasPrice" => "7000000000", + "gasUsed" => "37298", + "cumulativeGasUsed" => "1043649", + "input" => + "0xa9059cbb00000000000000000000000021e21ba085289f81a86921de890eed30f1ad23750000000000000000000000000000000000000000000000008ac7230489e80000", + "confirmations" => "199384" + } + ] + } + + @account_tokentx_example_value_error %{ + "status" => "0", + "message" => "No token transfers found", + "result" => [] + } + + @account_tokenbalance_example_value %{ + "status" => "1", + "message" => "OK", + "result" => "135499" + } + + @account_tokenbalance_example_value_error %{ + "status" => "0", + "message" => "Invalid address format", + "result" => nil + } + + @account_tokenlist_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "balance" => "135499", + "contractAddress" => "0x0000000000000000000000000000000000000000", + "name" => "Example Token", + "decimals" => "18", + "symbol" => "ET", + "type" => "ERC-20" + }, + %{ + "balance" => "1", + "contractAddress" => "0x0000000000000000000000000000000000000001", + "name" => "Example ERC-721 Token", + "decimals" => "18", + "symbol" => "ET7", + "type" => "ERC-721" + } + ] + } + + @account_getminedblocks_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "blockNumber" => "3462296", + "timeStamp" => "1491118514", + "blockReward" => "5194770940000000000" + } + ] + } + + @account_listaccounts_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "address" => "0x0000000000000000000000000000000000000000", + "balance" => "135499" + } + ] + } + + @account_getminedblocks_example_value_error %{ + "status" => "0", + "message" => "No blocks found", + "result" => [] + } + + @logs_getlogs_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "address" => "0x33990122638b9132ca29c723bdf037f1a891a70c", + "topics" => [ + "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + "0x72657075746174696f6e00000000000000000000000000000000000000000000", + "0x000000000000000000000000d9b2f59f3b5c7b3c67047d2f03c3e8052470be92" + ], + "data" => "0x", + "blockNumber" => "0x5c958", + "timeStamp" => "0x561d688c", + "gasPrice" => "0xba43b7400", + "gasUsed" => "0x10682", + "logIndex" => "0x", + "transactionHash" => "0x0b03498648ae2da924f961dda00dc6bb0a8df15519262b7e012b7d67f4bb7e83", + "transactionIndex" => "0x" + } + ] + } + + @logs_getlogs_example_value_error %{ + "status" => "0", + "message" => "Invalid address format", + "result" => nil + } + + @token_gettoken_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "cataloged" => true, + "contractAddress" => "0x0000000000000000000000000000000000000000", + "decimals" => "18", + "name" => "Example Token", + "symbol" => "ET", + "totalSupply" => "1000000000", + "type" => "ERC-20" + } + } + + @token_gettoken_example_value_error %{ + "status" => "0", + "message" => "Invalid contract address format", + "result" => nil + } + + @token_gettokenholders_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "address" => "0x0000000000000000000000000000000000000000", + "value" => "965208500001258757122850" + } + ] + } + + @token_gettokenholders_example_value_error %{ + "status" => "0", + "message" => "Invalid contract address format", + "result" => nil + } + + @stats_tokensupply_example_value %{ + "status" => "1", + "message" => "OK", + "result" => "21265524714464" + } + + @stats_ethsupplyexchange_example_value %{ + "status" => "1", + "message" => "OK", + "result" => "101959776311500000000000000" + } + + @stats_ethsupply_example_value %{ + "status" => "1", + "message" => "OK", + "result" => "101959776311500000000000000" + } + + @stats_coinsupply_example_value 101_959_776.3115 + + @stats_coinprice_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "coin_btc" => "0.03246", + "coin_btc_timestamp" => "1537212510", + "coin_usd" => "204", + "coin_usd_timestamp" => "1537212513" + } + } + + @stats_totalfees_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "total_fees" => "75411956011480008034" + } + } + + @stats_totalfees_example_value_error %{ + "status" => "0", + "message" => "An incorrect input date provided. It should be in ISO 8601 format (yyyy-mm-dd).", + "result" => nil + } + + @block_getblockreward_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "blockNumber" => "2165403", + "timeStamp" => "1472533979", + "blockMiner" => "0x13a06d3dfe21e0db5c016c03ea7d2509f7f8d1e3", + "blockReward" => "5314181600000000000", + "uncles" => nil, + "uncleInclusionReward" => nil + } + } + + @block_getblockreward_example_value_error %{ + "status" => "0", + "message" => "Invalid block number", + "result" => nil + } + + @block_getblocknobytime_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "blockNumber" => "2165403" + } + } + + @block_getblocknobytime_example_value_error %{ + "status" => "0", + "message" => "Invalid params", + "result" => nil + } + + @block_eth_block_number_example_value %{ + "jsonrpc" => "2.0", + "result" => "0xb33bf1", + "id" => 1 + } + + @contract_listcontracts_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "SourceCode" => """ + pragma solidity >0.4.24; + + contract Test { + constructor() public { b = hex"12345678901234567890123456789012"; } + event Event(uint indexed a, bytes32 b); + event Event2(uint indexed a, bytes32 b); + function foo(uint a) public { emit Event(a, b); } + bytes32 b; + } + """, + "ABI" => """ + [{ + "type":"event", + "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], + "name":"Event" + }, { + "type":"event", + "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], + "name":"Event2" + }, { + "type":"function", + "inputs": [{"name":"a","type":"uint256"}], + "name":"foo", + "outputs": [] + }] + """, + "ContractName" => "Test", + "CompilerVersion" => "v0.2.1-2016-01-30-91a6b35", + "OptimizationUsed" => "1" + } + ] + } + + @contract_getabi_example_value %{ + "status" => "1", + "message" => "OK", + "result" => + ~s([{"constant":false,"inputs":[{"name":"voucher_token","type":"bytes32"}],"name":"burn","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"voucher_token","type":"bytes32"}],"name":"is_expired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"voucher_token","type":"bytes32"}],"name":"is_burnt","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"voucher_token","type":"bytes32"},{"name":"_lifetime","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]) + } + + @contract_getabi_example_value_error %{ + "status" => "0", + "message" => "Contract source code not verified", + "result" => nil + } + + @contract_verify_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "SourceCode" => """ + pragma solidity >0.4.24; + + contract Test { + constructor() public { b = hex"12345678901234567890123456789012"; } + event Event(uint indexed a, bytes32 b); + event Event2(uint indexed a, bytes32 b); + function foo(uint a) public { emit Event(a, b); } + bytes32 b; + } + """, + "ABI" => """ + [{ + "type":"event", + "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], + "name":"Event" + }, { + "type":"event", + "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], + "name":"Event2" + }, { + "type":"function", + "inputs": [{"name":"a","type":"uint256"}], + "name":"foo", + "outputs": [] + }] + """, + "ContractName" => "Test", + "CompilerVersion" => "v0.2.1-2016-01-30-91a6b35", + "OptimizationUsed" => "1", + "IsProxy" => "true", + "ImplementationAddress" => "0x000000000000000000000000000000000000000e" + } + } + + @contract_verify_example_value_error %{ + "status" => "0", + "message" => "There was an error verifying the contract.", + "result" => nil + } + + @contract_verifysourcecode_example_value %{ + "message" => "OK", + "result" => "b080b96bd06ad1c9341c2afb7e3730311388544961acde94", + "status" => "1" + } + + @contract_checkverifystatus_example_value %{ + "message" => "OK", + "result" => "Pending in queue", + "status" => "1" + } + + @contract_getsourcecode_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "SourceCode" => """ + pragma solidity >0.4.24; + + contract Test { + constructor() public { b = hex"12345678901234567890123456789012"; } + event Event(uint indexed a, bytes32 b); + event Event2(uint indexed a, bytes32 b); + function foo(uint a) public { emit Event(a, b); } + bytes32 b; + } + """, + "ABI" => """ + [{ + "type":"event", + "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], + "name":"Event" + }, { + "type":"event", + "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], + "name":"Event2" + }, { + "type":"function", + "inputs": [{"name":"a","type":"uint256"}], + "name":"foo", + "outputs": [] + }] + """, + "ContractName" => "Test", + "CompilerVersion" => "v0.2.1-2016-01-30-91a6b35", + "OptimizationUsed" => "1", + "FileName" => "{sourcify path or empty}", + "IsProxy" => "true", + "ImplementationAddress" => "0x000000000000000000000000000000000000000e" + } + } + + @contract_getsourcecode_example_value_error %{ + "status" => "0", + "message" => "Invalid address hash", + "result" => nil + } + + @transaction_gettxinfo_example_value %{ + "status" => "1", + "result" => %{ + "blockNumber" => "3", + "confirmations" => "0", + "from" => "0x000000000000000000000000000000000000000c", + "gasLimit" => "91966", + "gasUsed" => "95123", + "gasPrice" => "100000", + "hash" => "0x0000000000000000000000000000000000000000000000000000000000000004", + "input" => "0x04", + "logs" => [ + %{ + "address" => "0x000000000000000000000000000000000000000e", + "data" => "0x00", + "topics" => ["First Topic", "Second Topic", "Third Topic", "Fourth Topic"] + } + ], + "success" => true, + "timeStamp" => "1541018182", + "to" => "0x000000000000000000000000000000000000000d", + "value" => "67612", + revertReason: "No credit of that type" + } + } + + @transaction_gettxreceiptstatus_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "status" => "1" + } + } + + @transaction_gettxreceiptstatus_example_value_error %{ + "status" => "0", + "message" => "Query parameter txhash is required", + "result" => nil + } + + @transaction_getstatus_example_value %{ + "status" => "1", + "message" => "OK", + "result" => %{ + "isError" => "1", + "errDescription" => "Out of gas" + } + } + + @transaction_getstatus_example_value_error %{ + "status" => "0", + "message" => "Query parameter txhash is required", + "result" => nil + } + + @status_type %{ + type: "status", + enum: ~s(["0", "1"]), + enum_interpretation: %{"0" => "error", "1" => "ok"} + } + + @jsonrpc_version_type %{ + type: "string", + example: ~s("2.0") + } + + @message_type %{ + type: "string", + example: ~s("OK") + } + + @hex_number_type %{ + type: "string", + example: ~s("767969") + } + + @id_type %{ + type: "string", + example: ~s("1") + } + + @wei_type %{ + type: "wei", + definition: &__MODULE__.wei_type_definition/1, + example: ~s("663046792267785498951364") + } + + @gas_type %{ + type: "gas", + definition: "A nonnegative number roughly equivalent to computational steps.", + example: ~s("122261") + } + + @address_hash_type %{ + type: "address hash", + definition: "A 160-bit code used for identifying accounts or contracts.", + example: ~s("0x95426f2bc716022fcf1def006dbc4bb81f5b5164") + } + + @stale_type %{ + type: "boolean", + definition: + "Represents whether or not the balance has not been checked in the last 24 hours, and will be rechecked.", + example: true + } + + @transaction_hash_type %{ + type: "transaction hash", + definition: + "Either a 20-byte address hash or, in the case of being a contract creation transaction, it is the RLP empty byte sequence. Used for identifying transactions.", + example: ~s("0x9c81f44c29ff0226f835cd0a8a2f2a7eca6db52a711f8211b566fd15d3e0e8d4") + } + + @block_number_type %{ + type: "block number", + definition: "A nonnegative number used to identify blocks.", + example: ~s("34092") + } + + @input_type %{ + type: "input", + definition: "Data sent along with the transaction. A variable-byte-length binary.", + example: ~s("0x797af627d02e23b68e085092cd0d47d6cfb54be025f37b5989c0264398f534c08af7dea9") + } + + @confirmation_type %{ + type: "confirmations", + definition: "A number equal to the current block height minus the transaction's block-number.", + example: ~s("6005998") + } + + @transaction_index_type %{ + type: "transaction index", + definition: "Index of the transaction in it's block.", + example: ~s("0") + } + + @token_name_type %{ + type: "string", + definition: "Name of the token.", + example: ~s("Some Token Name") + } + + @token_id_type %{ + type: "integer", + definition: "id of token", + example: ~s("0") + } + + @token_symbol_type %{ + type: "string", + definition: "Trading symbol of the token.", + example: ~s("SYMBOL") + } + + @token_decimal_type %{ + type: "integer", + definition: "Number of decimal places the token can be subdivided to.", + example: ~s("18") + } + + @revert_reason_type %{ + type: "revert_reason", + definition: "Revert reason of transaction.", + example: ~s("No credit of that type") + } + + @logs_details %{ + name: "Log Detail", + fields: %{ + address: @address_hash_type, + topics: %{ + type: "topics", + definition: "An array including the topics for the log.", + example: ~s(["0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"]) + }, + data: %{ + type: "data", + definition: "Non-indexed log parameters.", + example: ~s("0x") + }, + blockNumber: %{ + type: "block number", + definition: "A nonnegative number used to identify blocks.", + example: ~s("0x5c958") + }, + index: %{ + type: "log index", + definition: "A nonnegative number used to identify logs.", + example: ~s("1") + } + } + } + + @token_holder_details %{ + name: "Token holder Detail", + fields: %{ + address: @address_hash_type, + value: %{ + type: "value", + definition: "A nonnegative number used to identify the balance of the target token.", + example: ~s("1000000000000000000") + } + } + } + + @address_balance %{ + name: "AddressBalance", + fields: %{ + address: @address_hash_type, + balance: @wei_type, + stale: @stale_type + } + } + + @transaction %{ + name: "Transaction", + fields: %{ + blockNumber: @block_number_type, + timeStamp: %{ + type: "timestamp", + definition: "The transaction's block-timestamp.", + example: ~s("1439232889") + }, + hash: @transaction_hash_type, + nonce: %{ + type: "nonce", + definition: "A scalar value equal to the number of transactions sent by the sender prior to this transaction.", + example: ~s("0") + }, + blockHash: %{ + type: "block hash", + definition: "A 32-byte hash used for identifying blocks.", + example: ~s("0xd3cabad6adab0b52eb632c386ea194036805713682c62cb589b5abcd76de2159") + }, + transactionIndex: @transaction_index_type, + from: @address_hash_type, + to: @address_hash_type, + value: @wei_type, + gas: @gas_type, + gasPrice: @wei_type, + isError: %{ + type: "error", + enum: ~s(["0", "1"]), + enum_interpretation: %{"0" => "ok", "1" => "error"} + }, + txreceipt_status: @status_type, + input: @input_type, + contractAddress: @address_hash_type, + cumulativeGasUsed: @gas_type, + gasUsed: @gas_type, + confirmations: @confirmation_type + } + } + + @internal_transaction %{ + name: "InternalTransaction", + fields: %{ + blockNumber: @block_number_type, + timeStamp: %{ + type: "timestamp", + definition: "The transaction's block-timestamp.", + example: ~s("1439232889") + }, + from: @address_hash_type, + to: @address_hash_type, + value: @wei_type, + contractAddress: @address_hash_type, + input: @input_type, + type: %{ + type: "type", + 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: %{ + type: "error", + enum: ~s(["0", "1"]), + enum_interpretation: %{"0" => "ok", "1" => "rejected/cancelled"} + }, + errCode: %{ + type: "string", + definition: "Error message when call type error.", + example: ~s("Out of gas") + } + } + } + + @block_model %{ + name: "Block", + fields: %{ + blockNumber: @block_number_type, + timeStamp: %{ + type: "timestamp", + definition: "When the block was collated.", + example: ~s("1480072029") + } + } + } + + @token_transfer_model %{ + name: "TokenTransfer", + fields: %{ + blockNumber: @block_number_type, + timeStamp: %{ + type: "timestamp", + definition: "The transaction's block-timestamp.", + example: ~s("1439232889") + }, + hash: @transaction_hash_type, + nonce: %{ + type: "nonce", + definition: "A scalar value equal to the number of transactions sent by the sender prior to this transaction.", + example: ~s("0") + }, + blockHash: %{ + type: "block hash", + definition: "A 32-byte hash used for identifying blocks.", + example: ~s("0xd3cabad6adab0b52eb632c386ea194036805713682c62cb589b5abcd76de2159") + }, + from: @address_hash_type, + contractAddress: @address_hash_type, + to: @address_hash_type, + value: %{ + type: "integer", + definition: "The transferred amount.", + example: ~s("663046792267785498951364") + }, + tokenName: @token_name_type, + tokenID: @token_id_type, + tokenSymbol: @token_symbol_type, + tokenDecimal: @token_decimal_type, + transactionIndex: @transaction_index_type, + gas: @gas_type, + gasPrice: @wei_type, + gasUsed: @gas_type, + cumulativeGasUsed: @gas_type, + input: @input_type, + confirmations: @confirmation_type + } + } + + @log %{ + name: "Log", + fields: %{ + address: @address_hash_type, + topics: %{ + type: "topics", + definition: "An array including the topics for the log.", + example: ~s(["0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"]) + }, + data: %{ + type: "data", + definition: "Non-indexed log parameters.", + example: ~s("0x") + }, + blockNumber: %{ + type: "block number", + definition: "A nonnegative number used to identify blocks.", + example: ~s("0x5c958") + }, + timeStamp: %{ + type: "timestamp", + definition: "The transaction's block-timestamp.", + example: ~s("0x561d688c") + }, + gasPrice: %{ + type: "wei", + definition: &__MODULE__.wei_type_definition/1, + example: ~s("0xba43b7400") + }, + gasUsed: %{ + type: "gas", + definition: "A nonnegative number roughly equivalent to computational steps.", + example: ~s("0x10682") + }, + logIndex: %{ + type: "hexadecimal", + example: ~s("0x") + }, + transactionHash: @transaction_hash_type, + transactionIndex: %{ + type: "hexadecimal", + example: ~s("0x") + } + } + } + + @token_model %{ + name: "Token", + fields: %{ + name: @token_name_type, + symbol: @token_symbol_type, + totalSupply: %{ + type: "integer", + definition: "The total supply of the token.", + example: ~s("1000000000") + }, + decimals: @token_decimal_type, + type: %{ + type: "token type", + enum: ~s(["ERC-20", "ERC-721"]), + enum_interpretation: %{"ERC-20" => "ERC-20 token standard", "ERC-721" => "ERC-721 token standard"} + }, + cataloged: %{ + type: "boolean", + definition: "Flag for if token information has been cataloged.", + example: ~s(true) + }, + contractAddress: @address_hash_type + } + } + + @token_balance_model %{ + name: "TokenBalance", + fields: %{ + balance: %{ + type: "integer", + definition: "The token account balance.", + example: ~s("135499") + }, + name: @token_name_type, + symbol: @token_symbol_type, + decimals: @token_decimal_type, + contractAddress: @address_hash_type + } + } + + @block_reward_model %{ + name: "BlockReward", + fields: %{ + blockNumber: @block_number_type, + timeStamp: %{ + type: "timestamp", + definition: "When the block was collated.", + example: ~s("1480072029") + }, + blockMiner: @address_hash_type, + blockReward: %{ + type: "block reward", + definition: "The reward given to the miner of a block.", + example: ~s("5003251945421042780") + }, + uncles: %{type: "null"}, + uncleInclusionReward: %{type: "null"} + } + } + + @block_no_model %{ + name: "BlockNo", + fields: %{ + blockNumber: @block_number_type + } + } + + @account_model %{ + name: "Account", + fields: %{ + "address" => @address_hash_type, + "balance" => @wei_type + } + } + + @contract_model %{ + name: "Contract", + fields: %{ + "Address" => @address_hash_type, + "ABI" => %{ + type: "ABI", + definition: "JSON string for the contract's Application Binary Interface (ABI)", + example: """ + "[{ + \\"type\\":\\"event\\", + \\"inputs\\": [{\\"name\\":\\"a\\",\\"type\\":\\"uint256\\",\\"indexed\\":true},{\\"name\\":\\"b\\",\\"type\\":\\"bytes32\\",\\"indexed\\":false}], + \\"name\\":\\"Event\\" + }, { + \\"type\\":\\"event\\", + \\"inputs\\": [{\\"name\\":\\"a\\",\\"type\\":\\"uint256\\",\\"indexed\\":true},{\\"name\\":\\"b\\",\\"type\\":\\"bytes32\\",\\"indexed\\":false}], + \\"name\\":\\"Event2\\" + }, { + \\"type\\":\\"function\\", + \\"inputs\\": [{\\"name\\":\\"a\\",\\"type\\":\\"uint256\\"}], + \\"name\\":\\"foo\\", + \\"outputs\\": [] + }]" + """ + }, + "ContractName" => %{ + type: "string", + example: ~S("Some name") + }, + "OptimizationUsed" => %{ + type: "optimization used", + enum: ~s(["0", "1"]), + enum_interpretation: %{"0" => "false", "1" => "true"} + } + } + } + + @uid_response_model %{ + name: "UID", + fields: %{ + "UID" => %{ + type: "string", + definition: "Unique identifier of the verification attempt", + example: "b080b96bd06ad1c9341c2afb7e3730311388544961acde94" + } + } + } + + @status_response_model %{ + name: "Status", + fields: %{ + "status" => %{ + type: "string", + definition: "Current status of the verification attempt", + example: "`Pending in queue` | `Pass - Verified` | `Fail - Unable to verify` | `Unknown UID`" + } + } + } + + @contract_source_code_type %{ + type: "contract source code", + definition: "The contract's source code.", + example: """ + "pragma solidity >0.4.24; + + contract Test { + constructor() public { b = hex"12345678901234567890123456789012"; } + event Event(uint indexed a, bytes32 b); + event Event2(uint indexed a, bytes32 b); + function foo(uint a) public { emit Event(a, b); } + bytes32 b; + }" + """ + } + + @contract_decompiled_source_code_type %{ + type: "contract decompiled source code", + definition: "The contract's decompiled source code.", + example: """ + const name() = 'CryptoKitties' + const GEN0_STARTING_PRICE() = 10^16 + const GEN0_AUCTION_DURATION() = 86400 + const GEN0_CREATION_LIMIT() = 45000 + const symbol() = 'CK' + const PROMO_CREATION_LIMIT() = 5000 + def storage: + ceoAddress is addr # mask(160, 0) at storage #0 + cfoAddress is addr # mask(160, 0) at storage #1 + stor1.768 is uint16 => uint256 # mask(256, 768) at storage #1 + cooAddress is addr # mask(160, 0) at storage #2 + stor2.0 is uint256 => uint256 # mask(256, 0) at storage #2 + paused is uint8 # mask(8, 160) at storage #2 + stor2.256 is uint256 => uint256 # mask(256, 256) at storage #2 + stor3 is uint32 # + ... + """ + } + + @contract_decompiler_version_type %{ + type: "decompiler version", + definition: "When decompiled source code is present, the decompiler version with which it was generated.", + example: "decompiler.version" + } + + @contract_with_sourcecode_model @contract_model + |> put_in([:fields, "SourceCode"], @contract_source_code_type) + |> put_in([:fields, "DecompiledSourceCode"], @contract_decompiled_source_code_type) + |> put_in([:fields, "DecompilerVersion"], @contract_decompiler_version_type) + + @transaction_receipt_status_model %{ + name: "TransactionReceiptStatus", + fields: %{ + status: %{ + type: "status", + enum: ~s(["0", "1"]), + enum_interpretation: %{"0" => "fail", "1" => "pass"} + } + } + } + + @transaction_info_model %{ + name: "TransactionInfo", + fields: %{ + hash: @transaction_hash_type, + timeStamp: %{ + type: "timestamp", + definition: "The transaction's block-timestamp.", + example: ~s("1439232889") + }, + blockNumber: @block_number_type, + confirmations: @confirmation_type, + success: %{ + type: "boolean", + definition: "Flag for success during tx execution", + example: ~s(true) + }, + from: @address_hash_type, + to: @address_hash_type, + value: @wei_type, + input: @input_type, + gasLimit: @wei_type, + gasUsed: @gas_type, + gasPrice: @wei_type, + logs: %{ + type: "array", + array_type: @logs_details + }, + revertReason: @revert_reason_type + } + } + + @transaction_status_model %{ + name: "TransactionStatus", + fields: %{ + isError: %{ + type: "isError", + enum: ~s(["0", "1"]), + enum_interpretation: %{"0" => "pass", "1" => "error"} + }, + errDescription: %{ + type: "string", + example: ~s("Out of gas") + } + } + } + + @coin_price_model %{ + name: "CoinPrice", + fields: %{ + coin_btc: %{ + type: "coin_btc", + definition: &__MODULE__.coin_btc_type_definition/1, + example: ~s("0.03161") + }, + coin_btc_timestamp: %{ + type: "timestamp", + definition: "Last updated timestamp.", + example: ~s("1537234460") + }, + coin_usd: %{ + type: "coin_usd", + definition: &__MODULE__.coin_usd_type_definition/1, + example: ~s("197.57") + }, + coin_usd_timestamp: %{ + type: "timestamp", + definition: "Last updated timestamp.", + example: ~s("1537234460") + } + } + } + + @total_fees_model %{ + name: "TotalFees", + fields: %{ + total_fees: %{ + type: "total_fees", + definition: "Total transaction fees in Wei are paid by users to validators per day.", + example: ~s("75411956011480008034") + } + } + } + + @account_eth_get_balance_action %{ + name: "eth_get_balance", + description: + "Mimics Ethereum JSON RPC's eth_getBalance. Returns the balance as of the provided block (defaults to latest)", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "The address of the account." + } + ], + optional_params: [ + %{ + key: "block", + placeholder: "block", + type: "string", + description: """ + Either the block number as a string, or one of latest, earliest or pending + + latest will be the latest balance in a *consensus* block. + earliest will be the first recorded balance for the address. + pending will be the latest balance in consensus *or* nonconcensus blocks. + """ + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_eth_get_balance_example_value), + model: %{ + name: "Result", + fields: %{ + jsonrpc: @jsonrpc_version_type, + id: @id_type, + result: @hex_number_type + } + } + } + ] + } + + @account_balance_action %{ + name: "balance", + description: """ + Get balance for address. Also available through a GraphQL 'addresses' query. + + If the balance hasn't been updated in a long time, we will double check + with the node to fetch the absolute latest balance. This will not be + reflected in the current request, but once it is updated, subsequent requests + will show the updated balance. If you want to know whether or not we are checking + for another balance, use the `balancemulti` action. That contains a property + called `stale` that will let you know to recheck that balance in the near future. + """, + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying Accounts." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_balance_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: @wei_type + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_balance_example_value_error) + } + ] + } + + @account_balancemulti_action %{ + name: "balancemulti", + description: """ + Get balance for multiple addresses. Also available through a GraphQL 'addresses' query. + + If the balance hasn't been updated in a long time, we will double check + with the node to fetch the absolute latest balance. This will not be + reflected in the current request, but once it is updated, subsequent requests + will show the updated balance. You can know that this is taking place via + the `stale` attribute, which is set to `true` if a new balance is being fetched. + """, + required_params: [ + %{ + key: "address", + placeholder: "addressHash1,addressHash2,addressHash3", + type: "string", + description: + "A 160-bit code used for identifying Accounts. Separate addresses by comma. Maximum of 20 addresses." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_balancemulti_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @address_balance + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_balance_example_value_error) + } + ] + } + + @account_pendingtxlist_action %{ + name: "pendingtxlist", + description: "Get pending transactions by address.", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying Accounts." + } + ], + optional_params: [ + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_pendingtxlist_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @transaction + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_txlist_example_value_error) + } + ] + } + + @account_txlist_action %{ + name: "txlist", + description: + "Get transactions by address. Up to a maximum of 10,000 transactions. Also available through a GraphQL 'address' query.", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying Accounts." + } + ], + optional_params: [ + %{ + key: "sort", + type: "string", + description: + "A string representing the order by block number direction. Defaults to descending order. Available values: asc, desc" + }, + %{ + key: "start_block", + type: "integer", + description: "A nonnegative integer that represents the starting block number." + }, + %{ + key: "end_block", + type: "integer", + description: "A nonnegative integer that represents the ending block number." + }, + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + }, + %{ + key: "filter_by", + type: "string", + description: """ + A string representing the field to filter by. If none is given + it returns transactions that match to, from, or contract address. + Available values: to, from + """ + }, + %{ + key: "start_timestamp", + type: "unix timestamp", + description: "Represents the starting block timestamp." + }, + %{ + key: "end_timestamp", + type: "unix timestamp", + description: "Represents the ending block timestamp." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_txlist_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @transaction + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_txlist_example_value_error) + } + ] + } + + @account_txlistinternal_action %{ + name: "txlistinternal", + description: + "Get internal transactions by transaction or address hash. Up to a maximum of 10,000 internal transactions. Also available through a GraphQL 'transaction' query.", + required_params: [ + %{ + key: "txhash", + placeholder: "transactionHash", + type: "string", + description: + "Transaction hash. Hash of contents of the transaction. A transcation hash or address hash is required." + } + ], + optional_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying accounts. An address hash or transaction hash is required." + }, + %{ + key: "sort", + type: "string", + description: + "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: "start_block", + type: "integer", + description: + "A nonnegative integer that represents the starting block number. WARNING: Only available if 'address' is provided." + }, + %{ + key: "end_block", + type: "integer", + description: + "A nonnegative integer that represents the ending block number. WARNING: Only available if 'address' is provided." + }, + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction. WARNING: Only available if 'address' is provided." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction. WARNING: Only available if 'address' is provided." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_txlistinternal_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @internal_transaction + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_txlistinternal_example_value_error) + } + ] + } + + @account_tokentx_action %{ + name: "tokentx", + description: + "Get token transfer events by address. Up to a maximum of 10,000 token transfer events. Also available through a GraphQL 'token_transfers' query.", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying accounts." + } + ], + optional_params: [ + %{ + key: "contractaddress", + placeholder: "contractAddressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + }, + %{ + key: "sort", + type: "string", + description: + "A string representing the order by block number direction. Defaults to ascending order. Available values: asc, desc" + }, + %{ + key: "start_block", + type: "integer", + description: "A nonnegative integer that represents the starting block number." + }, + %{ + key: "end_block", + type: "integer", + description: "A nonnegative integer that represents the ending block number." + }, + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_tokentx_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @token_transfer_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_tokentx_example_value_error) + } + ] + } + + @account_tokenbalance_action %{ + name: "tokenbalance", + description: "Get token account balance for token contract address.", + required_params: [ + %{ + key: "contractaddress", + placeholder: "contractAddressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + }, + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying accounts." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_tokenbalance_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "integer", + definition: "The token account balance for the contract address.", + example: ~s("135499") + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_tokenbalance_example_value_error) + } + ] + } + + @account_tokenlist_action %{ + name: "tokenlist", + description: "Get list of tokens owned by address.", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying accounts." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_tokenlist_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @token_balance_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_tokenbalance_example_value_error) + } + ] + } + + @account_getminedblocks_action %{ + name: "getminedblocks", + description: "Get list of blocks mined by address.", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying accounts." + } + ], + optional_params: [ + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_getminedblocks_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @block_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@account_getminedblocks_example_value_error) + } + ] + } + + @account_listaccounts_action %{ + name: "listaccounts", + description: + "Get a list of accounts and their balances, sorted ascending by the time they were first seen by the explorer.", + required_params: [], + optional_params: [ + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@account_listaccounts_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @account_model + } + } + } + } + ] + } + + @logs_getlogs_action %{ + name: "getLogs", + description: "Get event logs for an address and/or topics. Up to a maximum of 1,000 event logs.", + required_params: [ + %{ + key: "fromBlock", + placeholder: "blockNumber", + type: "integer", + description: + "A nonnegative integer that represents the starting block number. The use of 'latest' is also supported." + }, + %{ + key: "toBlock", + placeholder: "blockNumber", + type: "integer", + description: + "A nonnegative integer that represents the ending block number. The use of 'latest' is also supported." + }, + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying contracts. An address and/or topic{x} is required." + }, + %{ + key: "topic0", + placeholder: "firstTopic", + type: "string", + description: "A string equal to the first topic. A topic{x} and/or address is required." + } + ], + optional_params: [ + %{ + key: "topic1", + type: "string", + description: "A string equal to the second topic. A topic{x} and/or address is required." + }, + %{ + key: "topic2", + type: "string", + description: "A string equal to the third topic. A topic{x} and/or address is required." + }, + %{ + key: "topic3", + type: "string", + description: "A string equal to the fourth topic. A topic{x} and/or address is required." + }, + %{ + key: "topic0_1_opr", + type: "string", + description: + "A string representing the and|or operator for topic0 and topic1. " <> + "Required if topic0 and topic1 is used. Available values: and, or" + }, + %{ + key: "topic0_2_opr", + type: "string", + description: + "A string representing the and|or operator for topic0 and topic2. " <> + "Required if topic0 and topic2 is used. Available values: and, or" + }, + %{ + key: "topic0_3_opr", + type: "string", + description: + "A string representing the and|or operator for topic0 and topic3. " <> + "Required if topic0 and topic3 is used. Available values: and, or" + }, + %{ + key: "topic1_2_opr", + type: "string", + description: + "A string representing the and|or operator for topic1 and topic2. " <> + "Required if topic1 and topic2 is used. Available values: and, or" + }, + %{ + key: "topic1_3_opr", + type: "string", + description: + "A string representing the and|or operator for topic1 and topic3. " <> + "Required if topic1 and topic3 is used. Available values: and, or" + }, + %{ + key: "topic2_3_opr", + type: "string", + description: + "A string representing the and|or operator for topic2 and topic3. " <> + "Required if topic2 and topic3 is used. Available values: and, or" + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@logs_getlogs_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @log + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@logs_getlogs_example_value_error) + } + ] + } + + @token_gettoken_action %{ + name: "getToken", + description: + "Get ERC-20 " <> + "or ERC-721 token by contract address.", + required_params: [ + %{ + key: "contractaddress", + placeholder: "contractAddressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@token_gettoken_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @token_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@token_gettoken_example_value_error) + } + ] + } + + @token_gettokenholders_action %{ + name: "getTokenHolders", + description: "Get token holders by contract address.", + required_params: [ + %{ + key: "contractaddress", + placeholder: "contractAddressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + } + ], + optional_params: [ + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@token_gettokenholders_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @token_holder_details + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@token_gettokenholders_example_value_error) + } + ] + } + + @stats_tokensupply_action %{ + name: "tokensupply", + description: + "Get ERC-20 or " <> + "ERC-721 " <> + " token total supply by contract address.", + required_params: [ + %{ + key: "contractaddress", + placeholder: "contractAddressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@stats_tokensupply_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "integer", + definition: "The total supply of the token.", + example: ~s("1000000000") + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@token_gettoken_example_value_error) + } + ] + } + + @stats_ethsupplyexchange_action %{ + name: "ethsupplyexchange", + description: "Get total supply in Wei from exchange.", + required_params: [], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@stats_ethsupplyexchange_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "integer", + description: "The total supply.", + example: ~s("101959776311500000000000000") + } + } + } + } + ] + } + + @stats_ethsupply_action %{ + name: "ethsupply", + description: "Get total supply in Wei from DB.", + required_params: [], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@stats_ethsupply_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "integer", + description: "The total supply in Wei from DB.", + example: ~s("101959776311500000000000000") + } + } + } + } + ] + } + + @stats_coinsupply_action %{ + name: "coinsupply", + description: "Get total coin supply from DB minus burnt number.", + required_params: [], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@stats_coinsupply_example_value), + model: %{ + name: "Result", + fields: %{ + result: %{ + type: "integer", + description: "The total supply from DB minus burnt number in coin dimension.", + example: 101_959_776.3115 + } + } + } + } + ] + } + + @stats_coinprice_action %{ + name: "coinprice", + description: "Get latest price of native coin in USD and BTC.", + required_params: [], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@stats_coinprice_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @coin_price_model + } + } + } + } + ] + } + + @stats_totalfees_action %{ + name: "totalfees", + description: "Gets total transaction fees in Wei are paid by users to validators per day.", + required_params: [ + %{ + key: "date", + placeholder: "date", + type: "string", + description: "day in ISO 8601 format (yyyy-mm-dd)" + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@stats_totalfees_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @total_fees_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@stats_totalfees_example_value_error) + } + ] + } + + @block_eth_block_number_action %{ + name: "eth_block_number", + description: "Mimics Ethereum JSON RPC's eth_blockNumber. Returns the lastest block number", + required_params: [], + optional_params: [ + %{ + key: "id", + placeholder: "request id", + type: "integer", + description: "A nonnegative integer that represents the json rpc request id." + } + ], + responses: [ + %{ + code: "200", + description: "successful request", + example_value: Jason.encode!(@block_eth_block_number_example_value), + model: %{ + name: "Result", + fields: %{ + jsonrpc: @jsonrpc_version_type, + id: @id_type, + result: @hex_number_type + } + } + } + ] + } + + @block_getblockreward_action %{ + name: "getblockreward", + description: "Get block reward by block number.", + required_params: [ + %{ + key: "blockno", + placeholder: "blockNumber", + type: "integer", + description: "A nonnegative integer that represents the block number." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@block_getblockreward_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @block_reward_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@block_getblockreward_example_value_error) + } + ] + } + + @block_getblocknobytime_action %{ + name: "getblocknobytime", + description: "Get Block Number by Timestamp.", + required_params: [ + %{ + key: "timestamp", + placeholder: "blockTimestamp", + type: "integer", + description: "A nonnegative integer that represents the block timestamp (Unix timestamp in seconds)." + }, + %{ + key: "closest", + placeholder: "before/after", + type: "string", + description: "Direction to find the closest block number to given timestamp. Available values: before/after." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@block_getblocknobytime_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @block_no_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@block_getblocknobytime_example_value_error) + } + ] + } + + @contract_listcontracts_action %{ + name: "listcontracts", + description: """ + Get a list of contracts, sorted ascending by the time they were first seen by the explorer. + + If you provide the filters `not_decompiled`(`4`) or `not_verified(4)` the results will not + be sorted for performance reasons. + """, + required_params: [], + optional_params: [ + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + }, + %{ + key: "filter", + type: "string", + description: + "verified|decompiled|unverified|not_decompiled|empty, or 1|2|3|4|5 respectively. This requests only contracts with that status." + }, + %{ + key: "not_decompiled_with_version", + 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: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_listcontracts_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @contract_model + } + } + } + } + ] + } + + @contract_verify_action %{ + name: "verify", + description: """ + Verify a contract with its source code and contract creation information. +
    +
    +

    curl POST example:

    +
    +
    +
    +
    +
    + curl -d '{"addressHash":"0xc63BB6555C90846afACaC08A0F0Aa5caFCB382a1","compilerVersion":"v0.5.4+commit.9549d8ff", + "contractSourceCode":"pragma solidity ^0.5.4; \ncontract Test {\n}","name":"Test","optimization":false}' + -H "Content-Type: application/json" -X POST "https://blockscout.com/poa/sokol/api?module=contract&action=verify" + +
    +
    +
    + """, + required_params: [ + %{ + key: "addressHash", + placeholder: "addressHash", + type: "string", + description: "The address of the contract." + }, + %{ + key: "name", + placeholder: "name", + type: "string", + description: "The name of the contract." + }, + %{ + key: "compilerVersion", + placeholder: "compilerVersion", + type: "string", + description: "The compiler version for the contract." + }, + %{ + key: "optimization", + placeholder: false, + type: "boolean", + description: "Whether or not compiler optimizations were enabled." + }, + %{ + key: "contractSourceCode", + placeholder: "contractSourceCode", + type: "string", + description: "The source code of the contract." + } + ], + optional_params: [ + %{ + key: "constructorArguments", + type: "string", + description: "The constructor argument data provided." + }, + %{ + key: "autodetectConstructorArguments", + placeholder: false, + type: "boolean", + description: "Whether or not automatically detect constructor argument." + }, + %{ + key: "evmVersion", + placeholder: "evmVersion", + type: "string", + description: "The EVM version for the contract." + }, + %{ + key: "optimizationRuns", + placeholder: "optimizationRuns", + type: "integer", + description: "The number of optimization runs used during compilation" + }, + %{ + key: "library1Name", + type: "string", + description: "The name of the first library used." + }, + %{ + key: "library1Address", + type: "string", + description: "The address of the first library used." + }, + %{ + key: "library2Name", + type: "string", + description: "The name of the second library used." + }, + %{ + key: "library2Address", + type: "string", + description: "The address of the second library used." + }, + %{ + key: "library3Name", + type: "string", + description: "The name of the third library used." + }, + %{ + key: "library3Address", + type: "string", + description: "The address of the third library used." + }, + %{ + key: "library4Name", + type: "string", + description: "The name of the fourth library used." + }, + %{ + key: "library4Address", + type: "string", + description: "The address of the fourth library used." + }, + %{ + key: "library5Name", + type: "string", + description: "The name of the fourth library used." + }, + %{ + key: "library5Address", + type: "string", + description: "The address of the fourth library used." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_verify_example_value), + type: "model", + model: @contract_model + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@contract_verify_example_value_error) + } + ] + } + + @contract_verify_via_sourcify_action %{ + name: "verify_via_sourcify", + description: """ + Verify a contract through Sourcify.
    + a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the repo
    + b) otherwise you have to upload source files and JSON metadata file(s). +
    +
    +

    POST body example:

    +
    +
    +
    +
    +
    + --6e1e4c11657c62dc1e4349d024de9e28
    + Content-Disposition: form-data; name="addressHash"
    +
    + 0xb77b7443e0F32F1FEBf0BE0fBd7124D135d0a525
    +
    + --6e1e4c11657c62dc1e4349d024de9e28
    + Content-Disposition: form-data; name="files[0]"; filename="contract.sol"
    + Content-Type: application/json
    +
    + ...Source code...
    +
    + --6e1e4c11657c62dc1e4349d024de9e28
    + Content-Disposition: form-data; name="files[1]"; filename="metadata.json"
    + Content-Type: application/json
    +
    + ...JSON metadata...
    +
    + --6e1e4c11657c62dc1e4349d024de9e28--
    + +
    +
    +
    + """, + required_params: [ + %{ + key: "addressHash", + placeholder: "addressHash", + type: "string", + description: "The address of the contract." + } + ], + optional_params: [ + %{ + key: "files", + type: "file[]", + description: "Array with sources and metadata files" + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_verify_example_value), + type: "model", + model: @contract_model + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@contract_verify_example_value_error) + } + ] + } + + @contract_verify_vyper_contract_action %{ + name: "verify_vyper_contract", + description: """ + Verify a vyper contract with its source code and contract creation information. +
    +
    +

    curl POST example:

    +
    +
    +
    +
    +
    + curl --location --request POST 'http://localhost:4000/api?module=contract&action=verify_vyper_contract' \ + --form 'contractSourceCode="SOURCE_CODE"' \ + --form 'name="Vyper_contract"' \ + --form 'addressHash="0xE60B1B8bD493569a3E945be50A6c89d29a560Fa1"' \ + --form 'compilerVersion="v0.2.12"' + +
    +
    +
    + """, + required_params: [ + %{ + key: "addressHash", + placeholder: "addressHash", + type: "string", + description: "The address of the contract." + }, + %{ + key: "name", + placeholder: "name", + type: "string", + description: "The name of the contract." + }, + %{ + key: "compilerVersion", + placeholder: "compilerVersion", + type: "string", + description: "The compiler version for the contract." + }, + %{ + key: "contractSourceCode", + placeholder: "contractSourceCode", + type: "string", + description: "The source code of the contract." + } + ], + optional_params: [ + %{ + key: "constructorArguments", + type: "string", + description: "The constructor argument data provided." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_verify_example_value), + type: "model", + model: @contract_model + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@contract_verify_example_value_error) + } + ] + } + + @contract_verifysourcecode_action %{ + name: "verifysourcecode", + description: """ + Verify a contract with Standard input JSON file. Its interface the same as Etherscan's API endpoint +
    +
    + """, + required_params: [ + %{ + name: "solidity-standard-json-input", + key: "codeformat", + placeholder: "solidity-standard-json-input", + type: "string", + description: "Format of sourceCode(supported only \"solidity-standard-json-input\")" + }, + %{ + key: "contractaddress", + placeholder: "contractaddress", + type: "string", + description: "The address of the contract." + }, + %{ + key: "contractname", + placeholder: "contractname", + type: "string", + description: + "The name of the contract. It could be empty string(\"\"), just contract name(\"ContractName\"), or filename and contract name(\"contracts/contract_1.sol:ContractName\")" + }, + %{ + key: "compilerversion", + placeholder: "compilerversion", + type: "string", + description: "The compiler version for the contract." + }, + %{ + key: "sourceCode", + placeholder: "sourceCode", + type: "string", + description: "Standard input json" + } + ], + optional_params: [ + %{ + key: "constructorArguements", + type: "string", + description: "The constructor argument data provided." + }, + %{ + key: "autodetectConstructorArguments", + placeholder: false, + type: "boolean", + description: "Whether or not automatically detect constructor argument." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_verifysourcecode_example_value), + type: "model", + model: @uid_response_model + } + ] + } + + @contract_checkverifystatus_action %{ + name: "checkverifystatus", + description: "Return status of the verification attempt (works in addition to verifysourcecode method)", + required_params: [ + %{ + key: "guid", + placeholder: "identifierString", + type: "string", + description: "A string used for identifying verification attempt" + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_checkverifystatus_example_value), + type: "model", + model: @status_response_model + } + ] + } + + @contract_getabi_action %{ + name: "getabi", + description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_getabi_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "abi", + definition: "JSON string for the Application Binary Interface (ABI)" + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@contract_getabi_example_value_error) + } + ] + } + + @contract_getsourcecode_action %{ + name: "getsourcecode", + description: "Get contract source code for verified contract. Also available through a GraphQL 'addresses' query.", + required_params: [ + %{ + key: "address", + placeholder: "addressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_getsourcecode_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @contract_with_sourcecode_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@contract_getsourcecode_example_value_error) + } + ] + } + + @transaction_gettxinfo_action %{ + name: "gettxinfo", + description: "Get transaction info.", + required_params: [ + %{ + key: "txhash", + placeholder: "transactionHash", + type: "string", + description: "Transaction hash. Hash of contents of the transaction." + } + ], + optional_params: [ + %{ + key: "index", + type: "integer", + description: "A nonnegative integer that represents the log index to be used for pagination." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@transaction_gettxinfo_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @transaction_info_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@transaction_gettxreceiptstatus_example_value_error) + } + ] + } + + @transaction_gettxreceiptstatus_action %{ + name: "gettxreceiptstatus", + description: "Get transaction receipt status. Also available through a GraphQL 'transaction' query.", + required_params: [ + %{ + key: "txhash", + placeholder: "transactionHash", + type: "string", + description: "Transaction hash. Hash of contents of the transaction." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@transaction_gettxreceiptstatus_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @transaction_receipt_status_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@transaction_gettxreceiptstatus_example_value_error) + } + ] + } + + @transaction_getstatus_action %{ + name: "getstatus", + description: "Get error status and error message. Also available through a GraphQL 'transaction' query.", + required_params: [ + %{ + key: "txhash", + placeholder: "transactionHash", + type: "string", + description: "Transaction hash. Hash of contents of the transaction." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@transaction_getstatus_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @transaction_status_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@transaction_getstatus_example_value_error) + } + ] + } + + @account_module %{ + name: "account", + actions: [ + @account_eth_get_balance_action, + @account_balance_action, + @account_balancemulti_action, + @account_pendingtxlist_action, + @account_txlist_action, + @account_txlistinternal_action, + @account_tokentx_action, + @account_tokenbalance_action, + @account_tokenlist_action, + @account_getminedblocks_action, + @account_listaccounts_action + ] + } + + @logs_module %{ + name: "logs", + actions: [@logs_getlogs_action] + } + + @token_module %{ + name: "token", + actions: [ + @token_gettoken_action, + @token_gettokenholders_action + ] + } + + @stats_module %{ + name: "stats", + actions: [ + @stats_tokensupply_action, + @stats_ethsupplyexchange_action, + @stats_ethsupply_action, + @stats_coinsupply_action, + @stats_coinprice_action, + @stats_totalfees_action + ] + } + + @block_module %{ + name: "block", + actions: [@block_getblockreward_action, @block_getblocknobytime_action, @block_eth_block_number_action] + } + + @contract_module %{ + name: "contract", + actions: [ + @contract_listcontracts_action, + @contract_getabi_action, + @contract_getsourcecode_action, + @contract_verify_action, + @contract_verify_via_sourcify_action, + @contract_verify_vyper_contract_action, + @contract_verifysourcecode_action, + @contract_checkverifystatus_action + ] + } + + @transaction_module %{ + name: "transaction", + actions: [ + @transaction_gettxinfo_action, + @transaction_gettxreceiptstatus_action, + @transaction_getstatus_action + ] + } + + @documentation [ + @account_module, + @logs_module, + @token_module, + @stats_module, + @block_module, + @contract_module, + @transaction_module + ] + + def get_documentation do + @documentation + end + + def wei_type_definition(coin) do + "The smallest subdenomination of #{coin}, " <> + "and thus the one in which all integer values of the currency are counted, is the Wei. " <> + "One #{coin} is defined as being 1018 Wei." + end + + def coin_btc_type_definition(coin) do + "#{coin} price in Bitcoin." + end + + def coin_usd_type_definition(coin) do + "#{coin} price in US dollars." + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/gettext.ex b/apps/block_scout_web/lib/block_scout_web/gettext.ex new file mode 100644 index 0000000..3d54e80 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule BlockScoutWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import BlockScoutWeb.Gettext + + # Simple translation + gettext "Here is the string to translate" + + # Plural translation + ngettext "Here is the string to translate", + "Here are the strings to translate", + 3 + + # Domain-based translation + dgettext "errors", "Here is the error message to translate" + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :block_scout_web +end 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 0000000..8f0283c --- /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 0000000..2f3ef3f --- /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 0000000..058b160 --- /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 new file mode 100644 index 0000000..9e0264e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -0,0 +1,401 @@ +defmodule BlockScoutWeb.Notifier do + @moduledoc """ + Responds to events by sending appropriate channel updates to front-end. + """ + + alias Absinthe.Subscription + + alias BlockScoutWeb.{ + AddressContractVerificationViaFlattenedCodeView, + AddressContractVerificationViaJsonView, + AddressContractVerificationViaMultiPartFilesView, + AddressContractVerificationViaStandardJsonInputView, + AddressContractVerificationVyperView, + Endpoint + } + + alias Explorer.{Chain, Market, Repo} + alias Explorer.Chain.{Address, InternalTransaction, TokenTransfer, Transaction} + alias Explorer.Chain.Supply.RSK + alias Explorer.Chain.Transaction.History.TransactionStats + alias Explorer.Counters.{AverageBlockTime, Helper} + alias Explorer.ExchangeRates.Token + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} + alias Phoenix.View + + @check_broadcast_sequence_period 500 + + def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do + Endpoint.broadcast("addresses:new_address", "count", %{count: Chain.address_estimated_count()}) + + addresses + |> Stream.reject(fn %Address{fetched_coin_balance: fetched_coin_balance} -> is_nil(fetched_coin_balance) end) + |> Enum.each(&broadcast_balance/1) + end + + def handle_event({:chain_event, :address_coin_balances, type, address_coin_balances}) + when type in [:realtime, :on_demand] do + Enum.each(address_coin_balances, &broadcast_address_coin_balance/1) + end + + def handle_event({:chain_event, :address_token_balances, type, address_token_balances}) + when type in [:realtime, :on_demand] do + Enum.each(address_token_balances, &broadcast_address_token_balance/1) + end + + def handle_event({:chain_event, :address_current_token_balances, type, address_current_token_balances}) + when type in [:realtime, :on_demand] do + Enum.each(address_current_token_balances, &broadcast_address_token_balance/1) + end + + def handle_event( + {:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result, conn}} + ) do + %{view: view, compiler: compiler} = select_contract_type_and_form_view(conn.params) + + contract_verification_result = + case contract_verification_result do + {:ok, _} = result -> + result + + {:error, changeset} -> + compiler_versions = fetch_compiler_version(compiler) + + result = + view + |> View.render_to_string("new.html", + changeset: changeset, + compiler_versions: compiler_versions, + evm_versions: CodeCompiler.allowed_evm_versions(), + address_hash: address_hash, + conn: conn, + retrying: true + ) + + {:error, result} + end + + Endpoint.broadcast( + "addresses:#{address_hash}", + "verification_result", + %{ + result: contract_verification_result + } + ) + end + + def handle_event({:chain_event, :block_rewards, :realtime, rewards}) do + if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do + broadcast_rewards(rewards) + end + end + + def handle_event({:chain_event, :blocks, :realtime, blocks}) do + last_broadcasted_block_number = Helper.fetch_from_cache(:number, :last_broadcasted_block) + + blocks + |> Enum.sort_by(& &1.number, :asc) + |> Enum.each(fn block -> + broadcast_latest_block?(block, last_broadcasted_block_number) + end) + end + + def handle_event({:chain_event, :exchange_rate}) do + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + + market_history_data = + case Market.fetch_recent_history() do + [today | the_rest] -> [%{today | closing_price: exchange_rate.usd_value} | the_rest] + data -> data + end + + exchange_rate_with_available_supply = + case Application.get_env(:explorer, :supply) do + RSK -> + %{exchange_rate | available_supply: nil, market_cap_usd: RSK.market_cap(exchange_rate)} + + _ -> + Map.from_struct(exchange_rate) + end + + Endpoint.broadcast("exchange_rate:new_rate", "new_rate", %{ + exchange_rate: exchange_rate_with_available_supply, + market_history_data: Enum.map(market_history_data, fn day -> Map.take(day, [:closing_price, :date]) end) + }) + end + + def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do + internal_transactions + |> Stream.map( + &(InternalTransaction.where_nonpending_block() + |> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index) + |> Repo.preload([:from_address, :to_address, transaction: :block])) + ) + |> Enum.each(&broadcast_internal_transaction/1) + end + + def handle_event({:chain_event, :token_transfers, :realtime, all_token_transfers}) do + transfers_by_token = Enum.group_by(all_token_transfers, fn tt -> to_string(tt.token_contract_address_hash) end) + + for {token_contract_address_hash, token_transfers} <- transfers_by_token do + Subscription.publish( + Endpoint, + token_transfers, + token_transfers: token_contract_address_hash + ) + + token_transfers_full = + token_transfers + |> Stream.map( + &(TokenTransfer + |> Repo.get_by( + block_hash: &1.block_hash, + transaction_hash: &1.transaction_hash, + token_contract_address_hash: &1.token_contract_address_hash, + log_index: &1.log_index + ) + |> Repo.preload([:from_address, :to_address, :token, transaction: :block])) + ) + + token_transfers_full + |> Enum.each(&broadcast_token_transfer/1) + end + end + + def handle_event({:chain_event, :transactions, :realtime, transactions}) do + transactions + |> Enum.map(& &1.hash) + |> Chain.hashes_to_transactions( + necessity_by_association: %{ + :block => :optional, + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional + } + ) + |> Enum.map(fn tx -> + # Disable parsing of token transfers from websocket for transaction tab because we display token transfers at a separate tab + Map.put(tx, :token_transfers, []) + end) + |> Enum.each(&broadcast_transaction/1) + end + + def handle_event({:chain_event, :transaction_stats}) do + today = Date.utc_today() + + [{:history_size, history_size}] = + Application.get_env(:block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, {:history_size, 30}) + + x_days_back = Date.add(today, -1 * history_size) + + date_range = TransactionStats.by_date_range(x_days_back, today) + stats = Enum.map(date_range, fn item -> Map.drop(item, [:__meta__]) end) + + Endpoint.broadcast("transactions:stats", "update", %{stats: stats}) + end + + def handle_event(_), do: nil + + def fetch_compiler_version(compiler) do + case CompilerVersion.fetch_versions(compiler) do + {:ok, compiler_versions} -> + compiler_versions + + {:error, _} -> + [] + end + end + + def select_contract_type_and_form_view(params) do + verification_from_metadata_json? = check_verification_type(params, "json:metadata") + + verification_from_standard_json_input? = check_verification_type(params, "json:standard") + + 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 + + view = + cond 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. + """ + def broadcast_blocks_indexed_ratio(ratio, finished?) do + Endpoint.broadcast("blocks:indexing", "index_status", %{ + ratio: Decimal.to_string(ratio), + finished: finished? + }) + 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 || + last_broadcasted_block_number < block.number - 4 -> + broadcast_block(block) + :ets.insert(:last_broadcasted_block, {:number, block.number}) + + last_broadcasted_block_number > block.number - 1 -> + broadcast_block(block) + + true -> + Task.start_link(fn -> + schedule_broadcasting(block) + end) + end + end + + defp schedule_broadcasting(block) do + :timer.sleep(@check_broadcast_sequence_period) + last_broadcasted_block_number = Helper.fetch_from_cache(:number, :last_broadcasted_block) + + if last_broadcasted_block_number == block.number - 1 do + broadcast_block(block) + :ets.insert(:last_broadcasted_block, {:number, block.number}) + else + schedule_broadcasting(block) + end + end + + defp broadcast_address_coin_balance(%{address_hash: address_hash, block_number: block_number}) do + Endpoint.broadcast("addresses:#{address_hash}", "coin_balance", %{ + block_number: block_number + }) + end + + defp broadcast_address_token_balance(%{address_hash: address_hash, block_number: block_number}) do + Endpoint.broadcast("addresses:#{address_hash}", "token_balance", %{ + block_number: block_number + }) + end + + defp broadcast_balance(%Address{hash: address_hash} = address) do + Endpoint.broadcast( + "addresses:#{address_hash}", + "balance_update", + %{ + address: address, + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + } + ) + end + + defp broadcast_block(block) do + preloaded_block = Repo.preload(block, [[miner: :names], :transactions, :rewards]) + average_block_time = AverageBlockTime.average_block_time() + + Endpoint.broadcast("blocks:new_block", "new_block", %{ + block: preloaded_block, + average_block_time: average_block_time + }) + + Endpoint.broadcast("blocks:#{to_string(block.miner_hash)}", "new_block", %{ + block: preloaded_block, + average_block_time: average_block_time + }) + end + + defp broadcast_rewards(rewards) do + preloaded_rewards = Repo.preload(rewards, [:address, :block]) + emission_reward = Enum.find(preloaded_rewards, fn reward -> reward.address_type == :emission_funds end) + + preloaded_rewards_except_emission = + Enum.reject(preloaded_rewards, fn reward -> reward.address_type == :emission_funds end) + + Enum.each(preloaded_rewards_except_emission, fn reward -> + Endpoint.broadcast("rewards:#{to_string(reward.address_hash)}", "new_reward", %{ + emission_funds: emission_reward, + validator: reward + }) + end) + end + + defp broadcast_internal_transaction(internal_transaction) do + Endpoint.broadcast("addresses:#{internal_transaction.from_address_hash}", "internal_transaction", %{ + address: internal_transaction.from_address, + internal_transaction: internal_transaction + }) + + if internal_transaction.to_address_hash != internal_transaction.from_address_hash do + Endpoint.broadcast("addresses:#{internal_transaction.to_address_hash}", "internal_transaction", %{ + address: internal_transaction.to_address, + internal_transaction: internal_transaction + }) + end + end + + defp broadcast_transaction(%Transaction{block_number: nil} = pending) do + broadcast_transaction(pending, "transactions:new_pending_transaction", "pending_transaction") + end + + defp broadcast_transaction(transaction) do + broadcast_transaction(transaction, "transactions:new_transaction", "transaction") + end + + defp broadcast_transaction(transaction, transaction_channel, event) do + Endpoint.broadcast("transactions:#{transaction.hash}", "collated", %{}) + + Endpoint.broadcast(transaction_channel, event, %{ + transaction: transaction + }) + + Endpoint.broadcast("addresses:#{transaction.from_address_hash}", event, %{ + address: transaction.from_address, + transaction: transaction + }) + + if transaction.to_address_hash != transaction.from_address_hash do + Endpoint.broadcast("addresses:#{transaction.to_address_hash}", event, %{ + address: transaction.to_address, + transaction: transaction + }) + end + end + + defp broadcast_token_transfer(token_transfer) do + broadcast_token_transfer(token_transfer, "token_transfer") + end + + defp broadcast_token_transfer(token_transfer, event) do + Endpoint.broadcast("addresses:#{token_transfer.from_address_hash}", event, %{ + address: token_transfer.from_address, + token_transfer: token_transfer + }) + + Endpoint.broadcast("tokens:#{token_transfer.token_contract_address_hash}", event, %{ + address: token_transfer.token_contract_address_hash, + token_transfer: token_transfer + }) + + if token_transfer.to_address_hash != token_transfer.from_address_hash do + Endpoint.broadcast("addresses:#{token_transfer.to_address_hash}", event, %{ + address: token_transfer.to_address, + token_transfer: token_transfer + }) + end + end +end 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 0000000..3f44c6d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -0,0 +1,117 @@ +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"] + + 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 filter_options(%{"filter" => filter}) do + parse_filter(filter, @allowed_filter_labels) + end + + def filter_options(_params), do: [] + + def type_filter_options(%{"type" => type}) do + parse_filter(type, @allowed_type_labels) + end + + def type_filter_options(_params), do: [] + + def method_filter_options(%{"method" => method}) do + parse_method_filter(method) + end + + def method_filter_options(_params), do: [] + + def parse_filter("[" <> filter, allowed_labels) do + filter + |> String.trim_trailing("]") + |> parse_filter(allowed_labels) + end + + # sobelow_skip ["DOS.StringToAtom"] + 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() + |> Enum.map(&String.to_atom/1) + 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" + ] +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/admin/check_owner_registered.ex b/apps/block_scout_web/lib/block_scout_web/plug/admin/check_owner_registered.ex new file mode 100644 index 0000000..b15fd16 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/admin/check_owner_registered.ex @@ -0,0 +1,29 @@ +defmodule BlockScoutWeb.Plug.Admin.CheckOwnerRegistered do + @moduledoc """ + Checks that an admin owner has registered. + + If the admin owner, the user is redirected to a page + with instructions of how to continue setup. + """ + + import Phoenix.Controller, only: [redirect: 2] + import Plug.Conn + + alias BlockScoutWeb.AdminRouter.Helpers, as: AdminRoutes + alias Explorer.Admin + alias Plug.Conn + + def init(opts), do: opts + + def call(%Conn{} = conn, _opts) do + case Admin.owner() do + {:ok, _} -> + conn + + {:error, :not_found} -> + conn + |> redirect(to: AdminRoutes.setup_path(conn, :configure)) + |> halt() + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/admin/require_admin_role.ex b/apps/block_scout_web/lib/block_scout_web/plug/admin/require_admin_role.ex new file mode 100644 index 0000000..bd11cd5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/admin/require_admin_role.ex @@ -0,0 +1,26 @@ +defmodule BlockScoutWeb.Plug.Admin.RequireAdminRole do + @moduledoc """ + Authorization plug requiring a user to be authenticated and have an admin role. + """ + + import Plug.Conn + + import Phoenix.Controller, only: [redirect: 2] + + alias BlockScoutWeb.AdminRouter.Helpers, as: AdminRoutes + alias Explorer.Admin + + def init(opts), do: opts + + def call(conn, _) do + with user when not is_nil(user) <- conn.assigns[:user], + {:ok, admin} <- Admin.from_user(user) do + assign(conn, :admin, admin) + else + _ -> + conn + |> redirect(to: AdminRoutes.session_path(conn, :new)) + |> halt() + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/allow_iframe.ex b/apps/block_scout_web/lib/block_scout_web/plug/allow_iframe.ex new file mode 100644 index 0000000..ee20311 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/allow_iframe.ex @@ -0,0 +1,14 @@ +defmodule BlockScoutWeb.Plug.AllowIframe do + @moduledoc """ + Allows for iframes by deleting the + [`X-Frame-Options` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) + """ + + alias Plug.Conn + + def init(opts), do: opts + + def call(conn, _opts) do + Conn.delete_resp_header(conn, "x-frame-options") + end +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 0000000..80c5301 --- /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 0000000..00a3af4 --- /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 0000000..95269a2 --- /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/fetch_user_from_session.ex b/apps/block_scout_web/lib/block_scout_web/plug/fetch_user_from_session.ex new file mode 100644 index 0000000..963beac --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/fetch_user_from_session.ex @@ -0,0 +1,20 @@ +defmodule BlockScoutWeb.Plug.FetchUserFromSession do + @moduledoc """ + Fetches a `t:Explorer.Accounts.User.t/0` record if a user id is found in the session. + """ + + import Plug.Conn + + alias Explorer.Accounts + + def init(opts), do: opts + + def call(conn, _opts) do + with user_id when not is_nil(user_id) <- get_session(conn, :user_id), + {:ok, user} <- Accounts.fetch_user(user_id) do + assign(conn, :user, user) + else + _ -> conn + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/plug/graphql.ex b/apps/block_scout_web/lib/block_scout_web/plug/graphql.ex new file mode 100644 index 0000000..e46cbed --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/plug/graphql.ex @@ -0,0 +1,12 @@ +defmodule BlockScoutWeb.Plug.GraphQL do + @moduledoc """ + Default query for GraphiQL interface. + """ + @default_transaction_hash "0x69e3923eef50eada197c3336d546936d0c994211492c9f947a24c02827568f9f" + + def default_query do + transaction_hash = System.get_env("GRAPHIQL_TRANSACTION") || @default_transaction_hash + + "{transaction(hash: \"#{transaction_hash}\") { hash, blockNumber, value, gasUsed }}" + 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 0000000..ff8d457 --- /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/prometheus/exporter.ex b/apps/block_scout_web/lib/block_scout_web/prometheus/exporter.ex new file mode 100644 index 0000000..c5febc5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/prometheus/exporter.ex @@ -0,0 +1,9 @@ +defmodule BlockScoutWeb.Prometheus.Exporter do + @moduledoc """ + Exports `Prometheus` metrics at `/metrics` + """ + + @dialyzer :no_match + + use Prometheus.PlugExporter +end diff --git a/apps/block_scout_web/lib/block_scout_web/prometheus/instrumenter.ex b/apps/block_scout_web/lib/block_scout_web/prometheus/instrumenter.ex new file mode 100644 index 0000000..a782d43 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/prometheus/instrumenter.ex @@ -0,0 +1,16 @@ +defmodule BlockScoutWeb.Prometheus.Instrumenter do + @moduledoc """ + Phoenix request metrics for `Prometheus`. + """ + + @dialyzer {:no_match, + [ + phoenix_channel_join: 3, + phoenix_channel_receive: 3, + phoenix_controller_call: 3, + phoenix_controller_render: 3, + setup: 0 + ]} + + use Prometheus.PhoenixInstrumenter +end diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex new file mode 100644 index 0000000..148bbbc --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -0,0 +1,41 @@ +defmodule BlockScoutWeb.RealtimeEventHandler do + @moduledoc """ + Subscribing process for broadcast events from realtime. + """ + + use GenServer + + alias BlockScoutWeb.Notifier + alias Explorer.Chain.Events.Subscriber + alias Explorer.Counters.Helper + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @impl true + def init([]) do + Helper.create_cache_table(:last_broadcasted_block) + Subscriber.to(:address_coin_balances, :realtime) + Subscriber.to(:addresses, :realtime) + Subscriber.to(:block_rewards, :realtime) + Subscriber.to(:blocks, :realtime) + Subscriber.to(:internal_transactions, :realtime) + Subscriber.to(:token_transfers, :realtime) + Subscriber.to(:transactions, :realtime) + Subscriber.to(:addresses, :on_demand) + Subscriber.to(:address_coin_balances, :on_demand) + Subscriber.to(:address_token_balances, :on_demand) + Subscriber.to(:contract_verification_result, :on_demand) + # Does not come from the indexer + Subscriber.to(:exchange_rate) + Subscriber.to(:transaction_stats) + {:ok, []} + end + + @impl true + def handle_info(event, state) do + Notifier.handle_event(event) + {:noreply, state} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/resolvers/address.ex b/apps/block_scout_web/lib/block_scout_web/resolvers/address.ex new file mode 100644 index 0000000..dda5aaf --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/resolvers/address.ex @@ -0,0 +1,19 @@ +defmodule BlockScoutWeb.Resolvers.Address do + @moduledoc false + + alias Explorer.Chain + + def get_by(_, %{hashes: hashes}, _) do + case Chain.hashes_to_addresses(hashes) do + [] -> {:error, "Addresses not found."} + result -> {:ok, result} + end + end + + def get_by(_, %{hash: hash}, _) do + case Chain.hash_to_address(hash) do + {:error, :not_found} -> {:error, "Address not found."} + {:ok, _} = result -> result + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/resolvers/block.ex b/apps/block_scout_web/lib/block_scout_web/resolvers/block.ex new file mode 100644 index 0000000..a970f7c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/resolvers/block.ex @@ -0,0 +1,12 @@ +defmodule BlockScoutWeb.Resolvers.Block do + @moduledoc false + + alias Explorer.Chain + + def get_by(_, %{number: number}, _) do + case Chain.number_to_block(number) do + {:ok, _} = result -> result + {:error, :not_found} -> {:error, "Block number #{number} was not found."} + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/resolvers/internal_transaction.ex b/apps/block_scout_web/lib/block_scout_web/resolvers/internal_transaction.ex new file mode 100644 index 0000000..08f3ca4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/resolvers/internal_transaction.ex @@ -0,0 +1,23 @@ +defmodule BlockScoutWeb.Resolvers.InternalTransaction do + @moduledoc false + + alias Absinthe.Relay.Connection + alias Explorer.Chain.Transaction + alias Explorer.{GraphQL, Repo} + + def get_by(%{transaction_hash: _, index: _} = args) do + GraphQL.get_internal_transaction(args) + end + + def get_by(%Transaction{} = transaction, args, _) do + transaction + |> GraphQL.transaction_to_internal_transactions_query() + |> Connection.from_query(&Repo.all/1, args, options(args)) + end + + defp options(%{before: _}), do: [] + + defp options(%{count: count}), do: [count: count] + + defp options(_), do: [] +end diff --git a/apps/block_scout_web/lib/block_scout_web/resolvers/token_transfer.ex b/apps/block_scout_web/lib/block_scout_web/resolvers/token_transfer.ex new file mode 100644 index 0000000..38b8b3f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/resolvers/token_transfer.ex @@ -0,0 +1,24 @@ +defmodule BlockScoutWeb.Resolvers.TokenTransfer do + @moduledoc false + + alias Absinthe.Relay.Connection + alias Explorer.{GraphQL, Repo} + + def get_by(%{transaction_hash: _, log_index: _} = args) do + GraphQL.get_token_transfer(args) + end + + def get_by(_, %{token_contract_address_hash: token_contract_address_hash} = args, _) do + connection_args = Map.take(args, [:after, :before, :first, :last]) + + token_contract_address_hash + |> GraphQL.list_token_transfers_query() + |> Connection.from_query(&Repo.all/1, connection_args, options(args)) + end + + defp options(%{before: _}), do: [] + + defp options(%{count: count}), do: [count: count] + + defp options(_), do: [] +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 new file mode 100644 index 0000000..adfe1b7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex @@ -0,0 +1,28 @@ +defmodule BlockScoutWeb.Resolvers.Transaction do + @moduledoc false + + alias Absinthe.Relay.Connection + alias Explorer.{Chain, GraphQL, Repo} + alias Explorer.Chain.Address + + def get_by(_, %{hash: hash}, _) do + case Chain.hash_to_transaction(hash) do + {:ok, transaction} -> {:ok, transaction} + {:error, :not_found} -> {:error, "Transaction not found."} + end + 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(args.order) + |> Connection.from_query(&Repo.all/1, connection_args, options(args)) + end + + defp options(%{before: _}), do: [] + + defp options(%{count: count}), do: [count: count] + + defp options(_), do: [] +end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex new file mode 100644 index 0000000..543dc4a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -0,0 +1,89 @@ +defmodule BlockScoutWeb.Router do + use BlockScoutWeb, :router + + alias BlockScoutWeb.Plug.GraphQL + alias BlockScoutWeb.{ApiRouter, WebRouter} + + if Application.compile_env(:block_scout_web, ApiRouter)[:wobserver_enabled] do + forward("/wobserver", Wobserver.Web.Router) + end + + if Application.compile_env(:block_scout_web, :admin_panel_enabled) do + forward("/admin", BlockScoutWeb.AdminRouter) + end + + pipeline :browser do + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(BlockScoutWeb.CSPHeader) + end + + pipeline :api do + plug(:accepts, ["json"]) + end + + forward("/api", ApiRouter) + + 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 + + forward("/graphql", Absinthe.Plug, + schema: BlockScoutWeb.Schema, + analyze_complexity: true, + max_complexity: @max_complexity + ) + + forward("/graphiql", Absinthe.Plug.GraphiQL, + schema: BlockScoutWeb.Schema, + interface: :advanced, + default_query: GraphQL.default_query(), + socket: BlockScoutWeb.UserSocket, + analyze_complexity: true, + max_complexity: @max_complexity + ) + else + scope "/", BlockScoutWeb do + pipe_through(:browser) + get("/api-docs", PageNotFoundController, :index) + get("/eth-rpc-api-docs", PageNotFoundController, :index) + end + end + + scope "/", BlockScoutWeb do + pipe_through(:browser) + + get("/api-docs", APIDocsController, :index) + get("/eth-rpc-api-docs", APIDocsController, :eth_rpc) + end + + url_params = Application.compile_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url] + api_path = url_params[:api_path] + path = url_params[:path] + + if path != api_path do + scope to_string(api_path) <> "verify_smart_contract" do + pipe_through(:api) + + post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create) + end + else + scope "/verify_smart_contract" do + pipe_through(:api) + + post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create) + end + end + + if Application.compile_env(:block_scout_web, WebRouter)[:enabled] do + forward("/", BlockScoutWeb.WebRouter) + else + scope "/", BlockScoutWeb do + pipe_through(:browser) + + forward("/", APIDocsController, :index) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/schema.ex b/apps/block_scout_web/lib/block_scout_web/schema.ex new file mode 100644 index 0000000..42d644c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schema.ex @@ -0,0 +1,124 @@ +defmodule BlockScoutWeb.Schema do + @moduledoc false + + use Absinthe.Schema + use Absinthe.Relay.Schema, :modern + + alias Absinthe.Middleware.Dataloader, as: AbsintheMiddlewareDataloader + alias Absinthe.Plugin, as: AbsinthePlugin + + alias BlockScoutWeb.Resolvers.{ + Address, + Block, + InternalTransaction, + TokenTransfer, + Transaction + } + + alias Explorer.Chain + alias Explorer.Chain.InternalTransaction, as: ExplorerChainInternalTransaction + alias Explorer.Chain.TokenTransfer, as: ExplorerChainTokenTransfer + alias Explorer.Chain.Transaction, as: ExplorerChainTransaction + + import_types(BlockScoutWeb.Schema.Types) + + node interface do + resolve_type(fn + %ExplorerChainInternalTransaction{}, _ -> + :internal_transaction + + %ExplorerChainTokenTransfer{}, _ -> + :token_transfer + + %ExplorerChainTransaction{}, _ -> + :transaction + + _, _ -> + nil + end) + end + + query do + node field do + resolve(fn + %{type: :internal_transaction, id: id}, _ -> + %{"transaction_hash" => transaction_hash_string, "index" => index} = Jason.decode!(id) + {:ok, transaction_hash} = Chain.string_to_transaction_hash(transaction_hash_string) + InternalTransaction.get_by(%{transaction_hash: transaction_hash, index: index}) + + %{type: :token_transfer, id: id}, _ -> + %{"transaction_hash" => transaction_hash_string, "log_index" => log_index} = Jason.decode!(id) + {:ok, transaction_hash} = Chain.string_to_transaction_hash(transaction_hash_string) + TokenTransfer.get_by(%{transaction_hash: transaction_hash, log_index: log_index}) + + %{type: :transaction, id: transaction_hash_string}, _ -> + {:ok, hash} = Chain.string_to_transaction_hash(transaction_hash_string) + Transaction.get_by(%{}, %{hash: hash}, %{}) + + _, _ -> + {:error, "Unknown node"} + end) + end + + @desc "Gets an address by hash." + field :address, :address do + arg(:hash, non_null(:address_hash)) + resolve(&Address.get_by/3) + end + + @desc "Gets addresses by address hash." + field :addresses, list_of(:address) do + arg(:hashes, non_null(list_of(non_null(:address_hash)))) + resolve(&Address.get_by/3) + complexity(fn %{hashes: hashes}, child_complexity -> length(hashes) * child_complexity end) + end + + @desc "Gets a block by number." + field :block, :block do + arg(:number, non_null(:integer)) + resolve(&Block.get_by/3) + end + + @desc "Gets token transfers by token contract address hash." + connection field(:token_transfers, node_type: :token_transfer) do + arg(:token_contract_address_hash, non_null(:address_hash)) + arg(:count, :integer) + + resolve(&TokenTransfer.get_by/3) + + complexity(fn + %{first: first}, child_complexity -> + first * child_complexity + + %{last: last}, child_complexity -> + last * child_complexity + end) + end + + @desc "Gets a transaction by hash." + field :transaction, :transaction do + arg(:hash, non_null(:full_hash)) + resolve(&Transaction.get_by/3) + end + end + + subscription do + field :token_transfers, list_of(:token_transfer) do + arg(:token_contract_address_hash, non_null(:address_hash)) + + config(fn args, _info -> + {:ok, topic: to_string(args.token_contract_address_hash)} + end) + end + end + + def context(context) do + loader = Dataloader.add_source(Dataloader.new(), :db, Chain.data()) + + Map.put(context, :loader, loader) + end + + def plugins do + [AbsintheMiddlewareDataloader] ++ AbsinthePlugin.defaults() + end +end 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 new file mode 100644 index 0000000..51ebae0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schema/scalars.ex @@ -0,0 +1,121 @@ +defmodule BlockScoutWeb.Schema.Scalars do + @moduledoc false + + use Absinthe.Schema.Notation + + alias Explorer.Chain.{Data, Hash, Wei} + alias Explorer.Chain.Hash.{Address, Full, Nonce} + + import_types(BlockScoutWeb.Schema.Scalars.JSON) + + @desc """ + The address (40 (hex) characters / 160 bits / 20 bytes) is derived from the public key (128 (hex) characters / + 512 bits / 64 bytes) which is derived from the private key (64 (hex) characters / 256 bits / 32 bytes). + + The address is actually the last 40 characters of the keccak-256 hash of the public key with `0x` appended. + """ + scalar :address_hash do + parse(fn + %Absinthe.Blueprint.Input.String{value: value} -> + Hash.cast(Address, value) + + _ -> + :error + end) + + serialize(&to_string/1) + end + + @desc """ + An unpadded hexadecimal number with 0 or more digits. Each pair of digits + maps directly to a byte in the underlying binary representation. When + interpreted as a number, it should be treated as big-endian. + """ + scalar :data do + parse(fn + %Absinthe.Blueprint.Input.String{value: value} -> + Data.cast(value) + + _ -> + :error + end) + + serialize(&to_string/1) + end + + @desc """ + A 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash. + """ + scalar :full_hash do + parse(fn + %Absinthe.Blueprint.Input.String{value: value} -> + Hash.cast(Full, value) + + _ -> + :error + end) + + serialize(&to_string/1) + end + + @desc """ + The nonce (16 (hex) characters / 128 bits / 8 bytes) is derived from the Proof-of-Work. + """ + scalar :nonce_hash do + parse(fn + %Absinthe.Blueprint.Input.String{value: value} -> + Hash.cast(Nonce, value) + + _ -> + :error + end) + + serialize(&to_string/1) + end + + @desc """ + The smallest fractional unit of Ether. Using wei instead of ether allows code to do integer match instead of using + floats. + + See [Ethereum Homestead Documentation](http://ethdocs.org/en/latest/ether.html) for examples of various denominations of wei. + + Etymology of "wei" comes from [Wei Dai (戴įļ­)](https://en.wikipedia.org/wiki/Wei_Dai), a + [cypherpunk](https://en.wikipedia.org/wiki/Cypherpunk) who came up with b-money, which outlined modern + cryptocurrencies. + """ + scalar :wei do + parse(fn + %Absinthe.Blueprint.Input.String{value: value} -> + Wei.cast(value) + + _ -> + :error + end) + + serialize(&to_string(&1.value)) + end + + enum :status do + value(:ok) + value(:error) + end + + enum :call_type do + value(:call) + value(:callcode) + value(:delegatecall) + value(:staticcall) + end + + enum :type do + value(:call) + value(:create) + 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/scalars/JSON.ex b/apps/block_scout_web/lib/block_scout_web/schema/scalars/JSON.ex new file mode 100644 index 0000000..8b4ef28 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schema/scalars/JSON.ex @@ -0,0 +1,33 @@ +defmodule BlockScoutWeb.Schema.Scalars.JSON do + @moduledoc """ + The JSON scalar type allows arbitrary JSON values to be passed in and out. + """ + use Absinthe.Schema.Notation + + @desc """ + The `JSON` scalar type represents arbitrary JSON string data, represented as UTF-8 + character sequences. The JSON type is most often used to represent a free-form + human-readable JSON string. + """ + scalar :json do + parse(&decode/1) + serialize(&encode/1) + end + + defp decode(%Absinthe.Blueprint.Input.String{value: value}) do + case Jason.decode(value) do + {:ok, result} -> {:ok, result} + _ -> :error + end + end + + defp decode(%Absinthe.Blueprint.Input.Null{}) do + {:ok, nil} + end + + defp decode(_) do + :error + end + + defp encode(value), do: Jason.encode!(value) +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 new file mode 100644 index 0000000..bc77abf --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schema/types.ex @@ -0,0 +1,176 @@ +defmodule BlockScoutWeb.Schema.Types do + @moduledoc false + + use Absinthe.Schema.Notation + use Absinthe.Relay.Schema.Notation, :modern + + import Absinthe.Resolution.Helpers + + alias BlockScoutWeb.Resolvers.{ + InternalTransaction, + Transaction + } + + import_types(Absinthe.Type.Custom) + import_types(BlockScoutWeb.Schema.Scalars) + + connection(node_type: :transaction) + connection(node_type: :internal_transaction) + connection(node_type: :token_transfer) + + @desc """ + A stored representation of a Web3 address. + """ + object :address do + field(:hash, :address_hash) + field(:fetched_coin_balance, :wei) + field(:fetched_coin_balance_block_number, :integer) + field(:contract_code, :data) + + field :smart_contract, :smart_contract do + resolve(dataloader(:db, :smart_contract)) + end + + 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 + %{first: first}, child_complexity -> + first * child_complexity + + %{last: last}, child_complexity -> + last * child_complexity + + %{}, _child_complexity -> + 0 + end) + end + end + + @desc """ + A package of data that contains zero or more transactions, the hash of the previous block ("parent"), and optionally + other data. Because each block (except for the initial "genesis block") points to the previous block, the data + structure that they form is called a "blockchain". + """ + object :block do + field(:hash, :full_hash) + field(:consensus, :boolean) + field(:difficulty, :decimal) + field(:gas_limit, :decimal) + field(:gas_used, :decimal) + field(:nonce, :nonce_hash) + field(:number, :integer) + field(:size, :integer) + field(:timestamp, :datetime) + field(:total_difficulty, :decimal) + field(:miner_hash, :address_hash) + field(:parent_hash, :full_hash) + end + + @desc """ + Models internal transactions. + """ + node object(:internal_transaction, id_fetcher: &internal_transaction_id_fetcher/2) do + field(:call_type, :call_type) + field(:created_contract_code, :data) + field(:error, :string) + field(:gas, :decimal) + field(:gas_used, :decimal) + field(:index, :integer) + field(:init, :data) + field(:input, :data) + field(:output, :data) + field(:trace_address, :json) + field(:type, :type) + field(:value, :wei) + field(:block_number, :integer) + field(:transaction_index, :integer) + field(:created_contract_address_hash, :address_hash) + field(:from_address_hash, :address_hash) + field(:to_address_hash, :address_hash) + field(:transaction_hash, :full_hash) + end + + @desc """ + The representation of a verified Smart Contract. + + "A contract in the sense of Solidity is a collection of code (its functions) + and data (its state) that resides at a specific address on the Ethereum + blockchain." + http://solidity.readthedocs.io/en/v0.4.24/introduction-to-smart-contracts.html + """ + object :smart_contract do + field(:name, :string) + field(:compiler_version, :string) + field(:optimization, :boolean) + field(:contract_source_code, :string) + field(:abi, :json) + field(:address_hash, :address_hash) + end + + @desc """ + Represents a token transfer between addresses. + """ + node object(:token_transfer, id_fetcher: &token_transfer_id_fetcher/2) do + field(:amount, :decimal) + field(:block_number, :integer) + field(:log_index, :integer) + field(:token_id, :decimal) + field(:from_address_hash, :address_hash) + field(:to_address_hash, :address_hash) + field(:token_contract_address_hash, :address_hash) + field(:transaction_hash, :full_hash) + end + + @desc """ + Models a Web3 transaction. + """ + node object(:transaction, id_fetcher: &transaction_id_fetcher/2) do + field(:hash, :full_hash) + field(:block_number, :integer) + field(:cumulative_gas_used, :decimal) + field(:error, :string) + field(:gas, :decimal) + field(:gas_price, :wei) + field(:gas_used, :decimal) + field(:index, :integer) + field(:input, :string) + field(:nonce, :nonce_hash) + field(:r, :decimal) + field(:s, :decimal) + field(:status, :status) + field(:v, :decimal) + field(:value, :wei) + field(:from_address_hash, :address_hash) + field(:to_address_hash, :address_hash) + field(:created_contract_address_hash, :address_hash) + + connection field(:internal_transactions, node_type: :internal_transaction) do + arg(:count, :integer) + resolve(&InternalTransaction.get_by/3) + + complexity(fn + %{first: first}, child_complexity -> + first * child_complexity + + %{last: last}, child_complexity -> + last * child_complexity + + %{}, _child_complexity -> + 0 + end) + end + end + + def token_transfer_id_fetcher(%{transaction_hash: transaction_hash, log_index: log_index}, _) do + Jason.encode!(%{transaction_hash: to_string(transaction_hash), log_index: log_index}) + end + + def transaction_id_fetcher(%{hash: hash}, _), do: to_string(hash) + + def internal_transaction_id_fetcher(%{transaction_hash: transaction_hash, index: index}, _) do + Jason.encode!(%{transaction_hash: to_string(transaction_hash), index: index}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/social_media.ex b/apps/block_scout_web/lib/block_scout_web/social_media.ex new file mode 100644 index 0000000..7c13c7a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/social_media.ex @@ -0,0 +1,25 @@ +defmodule BlockScoutWeb.SocialMedia do + @moduledoc """ + This module provides social media links + """ + + @services %{ + facebook: "https://www.facebook.com/", + instagram: "https://www.instagram.com/", + twitter: "https://www.twitter.com/", + telegram: "https://t.me/" + } + + def links do + :block_scout_web + |> Application.get_env(__MODULE__, []) + |> Enum.reverse() + |> filter_and_build_links() + end + + defp filter_and_build_links(configured_services) do + for {name, account} <- configured_services, Map.has_key?(@services, name) do + {name, @services[name] <> account} + end + end +end 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 0000000..a425a07 --- /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 0000000..72ebbe8 --- /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 0000000..1d83433 --- /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 0000000..618c877 --- /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 0000000..cf28ad5 --- /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 0000000..c54477c --- /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 0000000..409a4ff --- /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 0000000..36c2740 --- /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 0000000..a0b78f3 --- /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 0000000..86cfd1d --- /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 0000000..bd94519 --- /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 0000000..f7d2772 --- /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 0000000..ec4d930 --- /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 0000000..511405c --- /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 0000000..5fd4445 --- /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 0000000..40d9c03 --- /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 0000000..c1cca8a --- /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 0000000..55151ae --- /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 0000000..592532f --- /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 0000000..c2a6dce --- /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 0000000..95c92d9 --- /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/_balance_dropdown.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_dropdown.html.eex new file mode 100644 index 0000000..e590b9f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_dropdown.html.eex @@ -0,0 +1,12 @@ +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching tokens...") %> + + +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_block_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_block_link.html.eex new file mode 100644 index 0000000..935d6e7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_block_link.html.eex @@ -0,0 +1 @@ +<%= link(@block_number, to: block_path(@conn, :show, @block_number), class: "tile-title-lg") %> \ No newline at end of file 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 new file mode 100644 index 0000000..405f7e6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_current_coin_balance.html.eex @@ -0,0 +1,15 @@ +<%= format_wei_value(@coin_balance, :ether) %> +<%= if !empty_exchange_rate?(@exchange_rate) do %> + <% usd_value = to_string(@exchange_rate.usd_value) %> + + ( + ) + +<% end %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_custom_view_df_title.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_custom_view_df_title.html.eex new file mode 100644 index 0000000..fd99f0a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_custom_view_df_title.html.eex @@ -0,0 +1,19 @@ +
    + +
    + + <%= @title %> + + + + + + +
    +
    \ No newline at end of file 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 0000000..dbfb5ba --- /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/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex new file mode 100644 index 0000000..275a8f0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex @@ -0,0 +1,15 @@ +<%= if @address do %> + <%= if assigns[:show_full_hash] do %> + <%= if name = if assigns[:ignore_implementation_name], do: primary_name(@address), else: implementation_name(@address) || primary_name(@address) do %> + <%= name %> | + <% end %> + <%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address), "data-test": "address_hash_link", class: assigns[:class] do %> + <%= @address %> + <% end %> + + <% else %> + <%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address), "data-test": "address_hash_link", class: assigns[:class] do %> + <%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address: @address, contract: @contract, truncate: assigns[:truncate], use_custom_tooltip: @use_custom_tooltip, no_tooltip: assigns[:no_tooltip], custom_classes_tooltip: assigns[:custom_classes_tooltip], ignore_implementation_name: assigns[:ignore_implementation_name] %> + <% end %> + <% end %> +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_metatags.html.eex new file mode 100644 index 0000000..4e18247 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_metatags.html.eex @@ -0,0 +1,15 @@ +<%= if assigns[:address] do %> + + <%= gettext( + "%{address} - %{subnetwork} Explorer", + address: BlockScoutWeb.AddressView.address_page_title(@address), + subnetwork: LayoutView.subnetwork_title() + ) %> + + "> + Explorer.coin() <> ", "<> LayoutView.network_title() %>"> +<% else %> + + <%= gettext "Top Accounts - %{subnetwork} Explorer", subnetwork: LayoutView.subnetwork_title() %> + +<% 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 new file mode 100644 index 0000000..c3dc9af --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex @@ -0,0 +1,30 @@ + + <%= if name = if assigns[:ignore_implementation_name], do: primary_name(@address), else: implementation_name(@address) || primary_name(@address) do %> + <%= if assigns[:no_tooltip] do %> + <%= if @use_custom_tooltip == true do %> + <%= name %> (<%= short_hash(@address) %>...) + <% else %> + <%= short_contract_name(name, 30) %> + <%= short_contract_name(name, 10) %> + (<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>) + <% end %> + <% else %> + <%= if @use_custom_tooltip == true do %> + <%= name %> (<%= short_hash(@address) %>...) + <% else %> + "> + <%= short_contract_name(name, 30) %> + <%= short_contract_name(name, 10) %> + (<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>) + + <% end %> + <% end %> + <% else %> + <%= if assigns[:truncate] do %> + <%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %> + <% else %> + <%= @address %> + <%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %> + <% end %> + <% end %> + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_show_address_transactions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_show_address_transactions.html.eex new file mode 100644 index 0000000..0ce6401 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_show_address_transactions.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressTransactionView, "index.html", assigns %> \ No newline at end of file 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 new file mode 100644 index 0000000..ce533de --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex @@ -0,0 +1,107 @@ +<% dark_forest_addresses_list_0_4 = CustomContractsHelpers.get_custom_addresses_list(:dark_forest_addresses) %> +<% dark_forest_addresses_list_0_5 = CustomContractsHelpers.get_custom_addresses_list(:dark_forest_addresses_v_0_5) %> +<% dark_forest_addresses_list = dark_forest_addresses_list_0_4 ++ dark_forest_addresses_list_0_5 %> +<% current_address = "0x" <> Base.encode16(@address.hash.bytes, case: :lower) %> +
    + <%= link( + gettext("Transactions"), + class: "card-tab #{tab_status("transactions", @conn.request_path)}", + to: AccessHelpers.get_path(@conn, :address_transaction_path, :index, @address.hash) + ) %> + <%= if Chain.check_if_token_transfers_at_address(@address.hash) do %> + <%= link( + gettext("Token Transfers"), + class: "card-tab #{tab_status("token-transfers", @conn.request_path)}", + "data-test": "token_transfers_tab_link", + to: AccessHelpers.get_path(@conn, :address_token_transfers_path, :index, @address.hash) + ) %> + <% end %> + <%= if Chain.check_if_tokens_at_address(@address.hash) do %> + <%= link( + gettext("Tokens"), + class: "card-tab #{tab_status("tokens", @conn.request_path)}", + to: AccessHelpers.get_path(@conn, :address_token_path, :index, @address.hash), + "data-test": "tokens_tab_link" + ) %> + <% end %> + <%= link( + gettext("Internal Transactions"), + class: "card-tab #{tab_status("internal-transactions", @conn.request_path)}", + "data-test": "internal_transactions_tab_link", + to: AccessHelpers.get_path(@conn, :address_internal_transaction_path, :index, @address.hash) + ) %> + <%= link( + gettext("Coin Balance History"), + class: "card-tab #{tab_status("coin-balances", @conn.request_path)}", + "data-test": "coin_balance_tab_link", + to: AccessHelpers.get_path(@conn, :address_coin_balance_path, :index, @address.hash) + ) %> + <%= if Chain.check_if_logs_at_address(@address.hash) do %> + <%= link( + gettext("Logs"), + class: "card-tab #{tab_status("logs", @conn.request_path)}", + to: AccessHelpers.get_path(@conn, :address_logs_path, :index, @address.hash) + ) %> + <% end %> + <%= if Chain.check_if_validated_blocks_at_address(@address.hash) do %> + <%= link( + gettext("Blocks Validated"), + class: "card-tab #{tab_status("validations", @conn.request_path)}", + "data-test": "validations_tab_link", + to: AccessHelpers.get_path(@conn, :address_validation_path, :index, @address.hash) + ) %> + <% end %> + <%= if contract?(@address) do %> + <%= link( + to: AccessHelpers.get_path(@conn, :address_contract_path, :index, @address.hash), + class: "card-tab #{tab_status("contracts", @conn.request_path)}") do %> + <%= gettext("Code") %> + <%= if smart_contract_verified?(@address) do %> + <%= cond do %> + <% Enum.member?(dark_forest_addresses_list, current_address) -> %> + + <%= render BlockScoutWeb.IconsView, "_check_dark_forest_icon.html" %> + + <% true -> %> + + <% end %> + <% end %> + <% end %> + <% end %> + <%= if has_decompiled_code?(@address) do %> + <%= link( + to: AccessHelpers.get_path(@conn, :address_decompiled_contract_path, :index, @address.hash), + class: "card-tab #{tab_status("decompiled-contracts", @conn.request_path)}") do %> + <%= gettext("Decompiled code") %> + + <% end %> + <% end %> + <%= 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), + class: "card-tab #{tab_status("read-contract", @conn.request_path)}") + %> + <% end %> + <%= if @is_proxy do %> + <%= link( + gettext("Read Proxy"), + to: AccessHelpers.get_path(@conn, :address_read_proxy_path, :index, @address.hash), + class: "card-tab #{tab_status("read-proxy", @conn.request_path)}") + %> + <% end %> + <%= 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 %> + <%= link( + gettext("Write Proxy"), + to: AccessHelpers.get_path(@conn, :address_write_proxy_path, :index, @address.hash), + class: "card-tab #{tab_status("write-proxy", @conn.request_path)}") + %> + <% end %> +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex new file mode 100644 index 0000000..9e91a5a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex @@ -0,0 +1,34 @@ + + + + + <%= @index %> + + + + <%= @address |> BlockScoutWeb.AddressView.address_partial_selector(nil, nil) |> BlockScoutWeb.RenderHelpers.render_partial() %> + + + <%= balance(@address) %> + + + data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"> + <% end %> + + + <%= if balance_percentage_enabled?(@total_supply) do %> + + + <%= balance_percentage(@address, @total_supply) %> + + <% end %> + + + + <%= @tx_count %> + <%= gettext "Transactions sent" %> + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex new file mode 100644 index 0000000..60a022f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex @@ -0,0 +1,41 @@ + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer.html.eex new file mode 100644 index 0000000..24a31bb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer.html.eex @@ -0,0 +1,15 @@ +<%= if @type=="address" do %> + +<% else %> + +<% end %> + +
    +

    <%= @header %>

    + <%= if @type=="address" do %> +
    <%= address_link_to_other_explorer(@address_link, @hash ,true) %>
    + <% else %> +
    <%= address_link_to_other_explorer(@tx_link, @hash ,true) %>
    + <% end %> +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer_modal.html.eex new file mode 100644 index 0000000..69ffb70 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer_modal.html.eex @@ -0,0 +1,22 @@ +
    +
    + +
    +
    + + <%= if @type=="address" do %> + <%= link( + address_link_to_other_explorer(@address_link, @hash, false), + to: address_link_to_other_explorer(@address_link, @hash ,true) + ) %> + <% else %> + <%= link( + address_link_to_other_explorer(@tx_link, @hash, false), + to: address_link_to_other_explorer(@tx_link, @hash ,true) + ) %> + <% end %> + +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex new file mode 100644 index 0000000..21afb57 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex @@ -0,0 +1,38 @@ +
    + +
    +

    Verify with other Explorers:

    +
    + <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherscan.io", class: "etherscan", address_link: "https://etherscan.io/address/", tx_link: "https://etherscan.io/tx/" %> + <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Blockchair.com", class: "blockchair", address_link: "https://blockchair.com/ethereum/address/", tx_link: "https://blockchair.com/ethereum/transaction/" %> + <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherchain.org", class: "etherchain", address_link: "https://www.etherchain.org/account/", tx_link: "https://www.etherchain.org/tx/" %> + + + + + + +
    +
    + + + +
    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 new file mode 100644 index 0000000..57dba32 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex @@ -0,0 +1,58 @@ +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +

    <%= 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 %> +
    + +
    +
    + + + + + + + <%= if balance_percentage_enabled?(@total_supply) do %> + + <% end %> + + + + + <% columns_num = if balance_percentage_enabled?(@total_supply), do: 5, else: 4 %> + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: columns_num %> + +
    +
    +   +
    +
    +
    + Address +
    +
    +
    + Balance +
    +
    +
    + Percentage +
    +
    +
    + Txn Count +
    +
    +
    +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    +
    + + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex new file mode 100644 index 0000000..34059b2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex @@ -0,0 +1,292 @@ +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> + <% dark_forest_addresses_list_0_4 = CustomContractsHelpers.get_custom_addresses_list(:dark_forest_addresses) %> + <% dark_forest_addresses_list_0_5 = CustomContractsHelpers.get_custom_addresses_list(:dark_forest_addresses_v_0_5) %> + <% circles_addresses_list = CustomContractsHelpers.get_custom_addresses_list(:circles_addresses) %> + <% current_address = "0x" <> Base.encode16(@address.hash.bytes, case: :lower) %> + <% created_from_address_hash = if from_address_hash(@address), do: "0x" <> Base.encode16(from_address_hash(@address).bytes, case: :lower), else: nil %> +
    + +
    +
    +
    + <%= cond do %> + <% Enum.member?(dark_forest_addresses_list_0_4, current_address) -> %> + <%= render BlockScoutWeb.AddressView, "_custom_view_df_title.html", title: "zkSnark space warfare (v0.4)" %> + <% Enum.member?(dark_forest_addresses_list_0_5, current_address) -> %> + <%= render BlockScoutWeb.AddressView, "_custom_view_df_title.html", title: "zkSnark space warfare (v0.5)" %> + <% Enum.member?(circles_addresses_list, current_address) -> %> +
    + +
    + <% Enum.member?(circles_addresses_list, created_from_address_hash) -> %> +
    + +
    + <% true -> %> + <%= nil %> + <% end %> +

    +
    <%= address_title(@address) %> <%= gettext "Details" %>
    + <%= render BlockScoutWeb.AddressView, "_labels.html", address_hash: @address.hash, tags: @tags %> + + + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + id: "tx-raw-input", + additional_classes: ["overview-title-item"], + clipboard_text: @address, + aria_label: gettext("Copy Address"), + title: gettext("Copy Address") %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_qr_code.html" %> + +

    +

    <%= @address %>

    + + + <% address_name = primary_name(@address) %> + <%= cond do %> + <% @address.token -> %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Token name and symbol.") %> + <%= gettext("Token") %> +
    +
    + <%= link( + token_title(@address.token), + to: token_path(@conn, + :show, + @address.hash), + "data-test": + "token_hash_link" + ) + %> +
    +
    + <% address_name -> %> + <%= if contract?(@address) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The name found in the source code of the Contract.") %> + <%= gettext("Contract Name") %> +
    +
    + <%= short_contract_name(address_name, 30) %> +
    +
    + <% else %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The name of the validator.") %> + <%= gettext("Validator Name") %> +
    +
    + <%= short_contract_name(address_name, 30) %> +
    +
    + <% end %> + <% true -> %> + <% end %> + + <% from_address_hash = from_address_hash(@address) %> + <%= if contract?(@address) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Transactions and address of creation.") %> + <%= gettext("Creator") %> +
    +
    + <%= if from_address_hash do %> + <%= link( + trimmed_hash(from_address_hash(@address)), + to: address_path(@conn, :show, from_address_hash(@address)) + ) %> + + <%= gettext "at" %> + + <%= link( + trimmed_hash(transaction_hash(@address)), + to: transaction_path(@conn, :show, transaction_hash(@address)), + "data-test": "transaction_hash_link" + ) %> + <% else %> + + <% end %> +
    +
    + <% end %> + + <%= if @is_proxy do %> + <% {implementation_address, name} = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || "0x0000000000000000000000000000000000000000" %> + <%= if implementation_address do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Implementation address of the proxy contract.") %> + <%= gettext("Implementation") %> +
    +
    + <%= link( + (if name, do: name <> " | " <> implementation_address, else: implementation_address), + to: address_path(@conn, :show, implementation_address), + class: "contract-address" + ) + %> +
    +
    + <% end %> + <% end %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Address balance in") <> " " <> Explorer.coin_name() <> " " <> gettext("doesn't include ERC20, ERC721, ERC1155 tokens).") %> + <%= gettext("Balance") %> +
    +
    + <%= balance(@address) %> + <%= if !match?({:pending, _}, @coin_balance_status) && !empty_exchange_rate?(@exchange_rate) do %> + <% usd_value = to_string(@exchange_rate.usd_value) %> + + ( + ) + + <% end %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("All tokens in the account and total value.") %> + <%= gettext("Tokens") %> +
    +
    + <%= render BlockScoutWeb.AddressView, "_balance_dropdown.html", conn: @conn, address: @address %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Number of transactions related to this address.") %> + <%= gettext("Transactions") %> +
    +
    + <%= if @conn.request_path |> String.contains?("/transactions") do %> + + <%= if @address.transactions_count do %> + <%= Number.Delimit.number_to_delimited(@address.transactions_count, precision: 0) %> <%= gettext("Transactions") %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching transactions...") %> + <% end %> + + <% else %> + + <%= if @address.token_transfers_count do %> + <%= Number.Delimit.number_to_delimited(@address.transactions_count, precision: 0) %> <%= gettext("Transactions") %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching transactions...") %> + <% end %> + + <% end %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Number of transfers to/from this address.") %> + <%= gettext("Transfers") %> +
    +
    + <%= if @conn.request_path |> String.contains?("/token-transfers") do %> + + <%= if @address.token_transfers_count do %> + <%= Number.Delimit.number_to_delimited(@address.token_transfers_count, precision: 0) %> <%= gettext("Transfers") %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching transfers...") %> + <% end %> + + <% else %> + + <%= if @address.token_transfers_count do %> + <%= Number.Delimit.number_to_delimited(@address.token_transfers_count, precision: 0) %> <%= gettext("Transfers") %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching transfers...") %> + <% end %> + + <% end %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Gas used by the address.") %> + <%= gettext("Gas Used") %> +
    +
    + + <%= if @address.gas_used do %> + <%= Number.Delimit.number_to_delimited(@address.gas_used, precision: 0) %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching gas used...") %> + <% end %> + +
    +
    + + <%= if @address.fetched_coin_balance_block_number do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Block number in which the address was updated.") %> + <%= gettext("Last Balance Update") %> +
    +
    + <%= link( + @address.fetched_coin_balance_block_number, + to: block_path(@conn, :show, @address.fetched_coin_balance_block_number), + class: "tile-title-lg" + ) %> +
    +
    + <% end %> +
    + + +
    +
    +
    +
    +
    + + +<%= render BlockScoutWeb.CommonComponentsView, "_modal_qr_code.html", qr_code: qr_code(@address), title: @address %> + +<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex new file mode 100644 index 0000000..74e974b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex @@ -0,0 +1,31 @@ +
    +
    +
    + <%= link( + to: block_path(@conn, :show, @coin_balance.block_number), + class: "tile-title-lg" + ) do %> + <%= gettext "Block" %> <%= @coin_balance.block_number %> + <% end %> + <%= if @coin_balance.transaction_hash do %> + <%= link( + to: transaction_path(@conn, :show, @coin_balance.transaction_hash) + ) do %> + <%= @coin_balance.transaction_hash %> + <% end %> + <% end %> + +
    +
    + + <%= delta_arrow(@coin_balance.delta) %> + <%= format_delta(@coin_balance.delta) %> + +
    +
    + + <%= format(@coin_balance.value) %> + +
    +
    +
    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 new file mode 100644 index 0000000..9292206 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex @@ -0,0 +1,48 @@ +
    + + + <% 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, tags: @tags %> + +
    +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost, click to load newer blocks") %> + +

    <%= gettext "Balances" %>

    + + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading 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 %> + + + +
    +
    + <%= gettext "There is no coin history for this address." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..f3b11b6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -0,0 +1,224 @@ +<% contract_creation_code = contract_creation_code(@address) %> +<% 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) %> +<% 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 %> +
    + <% 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, tags: @tags %> + +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + <%= unless smart_contract_verified do %> + <%= if minimal_proxy_template do %> + <%= render BlockScoutWeb.CommonComponentsView, "_minimal_proxy_pattern.html", address_hash: metadata_for_verification.address_hash, conn: @conn %> + <% else %> + <%= if metadata_for_verification do %> + <% path = address_verify_contract_path(@conn, :new, @address.hash) %> +
    +
    + <%= 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, + 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") %> +
    + <% end %> + <% end %> + <% end %> + <%= if smart_contract_verified && @address.smart_contract.is_changed_bytecode do %> + <%= render BlockScoutWeb.CommonComponentsView, "_changed_bytecode_warning.html" %> + <% end %> + <%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %> + <% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %> + <%= if @address.smart_contract.partially_verified && smart_contract_verified do %> +
    + <%= gettext("This contract has been partially verified via Sourcify.") %> + <% else %> + <%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %> +
    + <%= gettext("This contract has been verified via Sourcify.") %> + <% end %> + <% end %> + <%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %> + target="_blank"> + View contract in Sourcify repository <%= render BlockScoutWeb.IconsView, "_external_link.html" %> + +
    + + <% end %> +
    +
    +
    <%= gettext "Contract name:" %>
    +
    <%= target_contract.name %>
    +


    +


    +
    <%= gettext "Optimization enabled" %>
    +
    <%= if target_contract.is_vyper_contract, do: "N/A", else: format_optimization_text(target_contract.optimization) %>
    +
    +
    +
    <%= gettext "Compiler version" %>
    +
    <%= target_contract.compiler_version %>
    +


    +


    + <%= if target_contract.optimization && target_contract.optimization_runs do %> +
    <%= gettext "Optimization runs" %>
    +
    <%= target_contract.optimization_runs %>
    + <% end %> +
    +
    + <%= if smart_contract_verified && target_contract.evm_version do %> +
    <%= gettext "EVM Version" %>
    +
    <%= target_contract.evm_version %>
    +


    +


    + <% end %> + <%= if target_contract.inserted_at do %> +
    <%= gettext "Verified at" %>
    +
    <%= target_contract.inserted_at %>
    + <% end %> +
    +
    + <%= if smart_contract_verified && target_contract.constructor_arguments do %> +
    +
    +

    <%= gettext "Constructor Arguments" %>

    +
    +
    +
    <%= raw(format_constructor_arguments(target_contract, @conn)) %>
    +              
    +
    +
    + <% end %> +
    +
    +

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

    + +
    +
    ><%= target_contract.contract_source_code %>
    +        
    + + <%= additional_sources |> Enum.with_index() |> Enum.map(fn {additional_source, index} -> %> +
    +
    +

    <%= additional_source.file_name %>

    + +
    +
    <%= additional_source.contract_source_code %>
    +          
    + <% end)%> + +
    +
    +

    <%= gettext "Contract ABI" %>

    + +
    +
    +
    <%= format_smart_contract_abi(target_contract.abi) %>
    +            
    +
    +
    + + <% end %> +
    + <%= case contract_creation_code do %> + <% {:selfdestructed, transaction_init} -> %> +
    +

    <%= gettext "Contract Creation Code" %>

    + +
    +
    +

    <%= gettext "Contracts that self destruct in their constructors have no contract code published and cannot be verified." %>

    +

    <%= gettext "Displaying the init data provided of the creating transaction." %>

    +
    +
    +
    <%= transaction_init %>
    +
    + <% {:ok, contract_code} -> %> + <%= if creation_code(@address) do %> +
    +

    <%= gettext "Contract Creation Code" %>

    +
    + + <%= 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 %> + <%= if fully_verified do %> +
    +

    <%= gettext "Deployed ByteCode" %>

    + +
    + <% else %> +
    +
    +

    <%= gettext "Deployed ByteCode" %>

    +
    +
    + + <%= 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 %> +
    +
    + <% end %> +
    +
    <%= contract_code %>
    +
    + <% end %> +
    + + <%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %> + <% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %> + <%= if target_contract.external_libraries && target_contract.external_libraries != [] do %> +
    +
    +

    <%= gettext "External libraries" %>

    +
    +
    +
    <%= raw(format_external_libraries(target_contract.external_libraries, @conn)) %>
    +              
    +
    +
    + <% end %> + <% 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 new file mode 100644 index 0000000..0a9f89f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex @@ -0,0 +1,129 @@ +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> + +
    +

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

    + + <%= form_for @changeset, + address_contract_verification_path(@conn, :create), + [], + fn f -> %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %> + +
    +
    + <%= label f, "Verify" %> +
    +
    +
    + <%= radio_button f, :verify_via, true, checked: true, class: "form-check-input verify-via-flattened-code", "aria-describedby": "verify_via-help-block" %> +
    + <%= label :verify_via, :true, gettext("Via flattened source code"), class: "radio-text" %> +
    +
    + <%= radio_button f, :verify_via, false, class: "form-check-input verify-via-standard-json-input" %> +
    + <%= label :verify_via, :false, gettext("Via Standard Input JSON"), class: "radio-text" %> +
    + <%= if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do %> +
    + <%= radio_button f, :verify_via, false, class: "form-check-input verify-via-sourcify" %> +
    + <%= label :verify_via, :false, gettext("Via Sourcify: Sources and metadata JSON file"), class: "radio-text" %> +
    + <% end %> + <%= 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" %> +
    + <%= label :verify_via, :false, gettext("Vyper contract"), class: "radio-text" %> +
    +
    + <%= error_tag f, :verify_via, id: "verify_via-help-block", class: "text-danger form-error" %> +
    +
    Choose a smart-contract verification method. Currently, Blockscout supports 2 methods:
    + 1. Verification through flattened source code. +
    + 2. Verification using Standard input JSON file.
    + 3. Verification through Sourcify.
    + a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the repo
    + b) otherwise you will be asked to upload source files and JSON metadata file(s).
    + 4. Verification of Vyper contract. +
    + +
    +
    + +
    + + <%= link( + gettext("Next"), + to: address_verify_contract_via_flattened_code_path(@conn, :new, @address_hash), + id: "verify_via_flattened_code_button", + class: "btn-full-primary mr-2", + "data-button-loading": "animation" + ) %> + <%= link( + gettext("Next"), + to: address_verify_contract_via_json_path(@conn, :new, @address_hash), + id: "verify_via_sourcify_button", + class: "btn-full-primary mr-2", + style: "display: none;", + "data-button-loading": "animation" + ) %> + <%= link( + gettext("Next"), + to: address_verify_vyper_contract_path(@conn, :new, @address_hash), + id: "verify_vyper_contract_button", + class: "btn-full-primary mr-2", + style: "display: none;", + "data-button-loading": "animation" + ) %> + <%= link( + gettext("Next"), + to: address_verify_contract_via_standard_json_input_path(@conn, :new, @address_hash), + 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" + ) %> + <%= + 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_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 new file mode 100644 index 0000000..fd98aaf --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex @@ -0,0 +1,10 @@ +
    +
    + <%= label @f, :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" %> +
    +
    <%= raw @tooltip %>
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex new file mode 100644 index 0000000..5fa7785 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex @@ -0,0 +1,10 @@ +
    +
    + <%= label @f, :constructor_arguments, gettext("ABI-encoded Constructor Arguments (if required by the contract)") %> +
    + <%= textarea @f, :constructor_arguments, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %> + <%= error_tag @f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger form-error", "data-test": "contract-constructor-arguments-error" %> +
    +
    Add arguments in ABI hex encoded form. Constructor arguments are written right to left, and will be found at the end of the input created bytecode. They may also be parsed here.
    +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex new file mode 100644 index 0000000..6084b3b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex @@ -0,0 +1,10 @@ +
    +
    + <%= label @f, :address_hash, gettext("Contract Address") %> +
    + <%= text_input @f, :address_hash, class: "form-control border-rounded", id: "smart_contract_address_hash", "aria-describedby": "contract-address-help-block", readonly: true %> + <%= error_tag @f, :address_hash, id: "contract-address-help-block", class: "text-danger form-error" %> +
    +
    The 0x address supplied on contract creation.
    +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex new file mode 100644 index 0000000..f4c39dd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex @@ -0,0 +1,10 @@ +
    +
    + <%= label @f, :name, gettext("Contract Name") %> +
    + <%= text_input @f, :name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block", "data-test": "contract_name" %> + <%= error_tag @f, :name, id: "contract-name-help-block", class: "text-danger form-error" %> +
    +
    <%= raw @tooltip %>
    +
    +
    \ No newline at end of file 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 new file mode 100644 index 0000000..fcc2109 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex @@ -0,0 +1,20 @@ +
    +
    + <%= label @f, "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" %> +
    +
    + <%= 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" %> +
    +
    + <%= error_tag @f, :autodetect_constructor_args, id: "autodetect_constructor_args-help-block", class: "text-danger form-error" %> +
    +
    +
    \ No newline at end of file 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 new file mode 100644 index 0000000..275de35 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex @@ -0,0 +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" %> +
    +
    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 0000000..7c6af84 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_libraries_other.html.eex @@ -0,0 +1,8 @@ +<%= for library_index <- 2..Application.get_env(:block_scout_web, :verification_max_libraries) do %> + <% library = "library" <> to_string(library_index) |> String.to_atom() %> +
    + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", library: library, index: library_index %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", library: library, 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 new file mode 100644 index 0000000..e86bb67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex @@ -0,0 +1,10 @@ +<% library_address = "library" <> to_string(@index) <> "_address" |> String.to_atom() %> +
    +
    + <%= label :external_libraries, @library, gettext("Library") <> " " <> to_string(@index) <> " " <> gettext("Address") %> +
    + <%= 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 0000000..995d86c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex @@ -0,0 +1,13 @@ +
    + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", + library: :library1, + 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", + library: :library1, + 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 new file mode 100644 index 0000000..b1e0a7a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex @@ -0,0 +1,10 @@ +<% library_name = "library" <> to_string(@index) <> "_name" |> String.to_atom() %> +
    +
    + <%= label :external_libraries, @library, 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_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 new file mode 100644 index 0000000..7728537 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex @@ -0,0 +1,119 @@ +<% 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 %> +<% 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") %> + +
    +

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

    + + <%= form_for changeset, + address_contract_verification_path(@conn, :create), + [], + fn f -> %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %> + + <%= 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: 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") %> +
    + <%= 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" %> +
    +
    +
    + <%= radio_button f, :optimization, false, class: "form-check-input optimization-false" %> +
    + <%= label :smart_contract_optimization, :false, gettext("No"), class: "radio-text" %> +
    +
    + <%= radio_button f, :optimization, true, class: "form-check-input optimization-true", "aria-describedby": "optimization-help-block" %> +
    + <%= label :smart_contract_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, :name, gettext("Optimization runs") %> +
    + <%= text_input f, :optimization_runs, class: "form-control border-rounded", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %> +
    +
    +
    +
    + +
    +
    + <%= label f, :contract_source_code, gettext("Enter the Solidity Contract Code") %> +
    + <%= 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" %> +
    +
    <%= 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" %>.
    +
    +
    + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_fetch_constructor_args.html", f: f %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_constructor_args.html", f: f, display_constructor_arguments_text_area: display_constructor_arguments_text_area %> + +
    + <%= 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": "" %> + <%= 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_json/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex new file mode 100644 index 0000000..f2bdbff --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex @@ -0,0 +1,49 @@ +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> + +
    +

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

    + <%= form_for @changeset, + address_contract_verification_path(@conn, :create), + [id: "metadata-json-dropzone-form"], + fn f -> %> + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %> + +
    +
    + +
    +
    +
    + <%= gettext("Drop sources and metadata JSON file or click here") %> + <%= error_tag f, :file, id: "file-help-block", class: "text-danger form-error", style: "max-width: 600px;" %> +
    +
    +
    +
    Drop all Solidity contract source files and JSON metadata file(s) created during contract compilation into the drop zone.
    +
    +
    + +
    + + + <%= 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_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 0000000..1cee4b5 --- /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") %> + +
    +

    <%= 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 :evm_version, :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" %> +
    +
    +
    + <%= radio_button f, :optimization, false, class: "form-check-input optimization-false" %> +
    + <%= label :smart_contract_optimization, :false, gettext("No"), class: "radio-text" %> +
    +
    + <%= radio_button f, :optimization, true, class: "form-check-input optimization-true", "aria-describedby": "optimization-help-block" %> +
    + <%= label :smart_contract_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, :name, 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;" %> +
    +
    +
    +
    <%= 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 new file mode 100644 index 0000000..e86c158 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex @@ -0,0 +1,63 @@ +<% 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 %> +<% 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") %> + +
    +

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

    + <%= form_for changeset, + address_contract_verification_path(@conn, :create), + [id: "standard-json-dropzone-form"], + fn f -> %> + <%= 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. Also contract name could be: path/to/file.sol:MyContract" %> + + <%= 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." %> + +
    +
    + +
    +
    +
    + <%= gettext("Drop the standard input JSON file or click here") %> + <%= error_tag f, :file, id: "file-help-block", class: "text-danger form-error", style: "max-width: 600px;" %> +
    +
    +
    +
    Drop the standard input JSON file created during contract compilation into the drop zone.
    +
    +
    + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_fetch_constructor_args.html", f: f %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_constructor_args.html", f: f, display_constructor_arguments_text_area: display_constructor_arguments_text_area %> + +
    + + + <%= 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_vyper/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex new file mode 100644 index 0000000..4b07896 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex @@ -0,0 +1,59 @@ +<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_vyper_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %> +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> + +
    +

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

    + + <%= form_for changeset, + address_contract_verification_path(@conn, :create), + [], + fn f -> %> + + <%= 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." %> + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_compiler_field.html", f: f, compiler_versions: @compiler_versions, tooltip: "" %> + +
    +
    + <%= label f, :contract_source_code, gettext("Enter the Vyper Contract Code") %> +
    + <%= textarea f, :contract_source_code, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-source-code-help-block" %> + <%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger form-error", "data-test": "contract-source-code-error" %> +
    +
    +
    +
    + + <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_constructor_args.html", f: f, display_constructor_arguments_text_area: "block" %> + +
    + +
    + +
    + + <%= 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( + 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_decompiled_contract/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..18abbbc --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex @@ -0,0 +1,33 @@ +
    + <% 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, tags: @tags %> +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> + <% contract = last_decompiled_contract_version(@address.decompiled_smart_contracts) %> + <%= if contract do %> +
    +

    <%= gettext "Decompiler version" %>

    +
    +
    <%= contract.decompiler_version %>
    +
    +
    +
    +
    +

    <%= gettext "Decompiled contract code" %>

    + +
    +
    +
    <%= raw(highlight_decompiled_code(contract.decompiled_source_code)) %>
    +
    +
    +
    + <% else %> +
    + <%= gettext "There is no decompilded contracts for this address." %> +
    + <% end %> +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..feb7011 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex @@ -0,0 +1,72 @@ +
    + <% 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, tags: @tags %> + +
    +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost, click to load newer internal transactions") %> +
    +

    <%= gettext "Internal Transactions" %>

    +
    + + <%= 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 %> +
    +
    + + +
    +
    + <%= gettext "There are no internal transactions for this address." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "internal-transactions", conn: @conn %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + +
    +
    + + +
    +
    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 new file mode 100644 index 0000000..eaa2d7d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex @@ -0,0 +1,111 @@ +
    "> + <% decoded_result = decode(@log, @log.transaction) %> + <%= case decoded_result do %> + <% {:error, :contract_not_verified, _cadidates} -> %> +
    + <%= gettext "To see accurate decoded input data, the contract must be verified." %> + <%= case @log.transaction do %> + <% %{to_address: %{hash: hash}} -> %> + <% path = address_verify_contract_path(@conn, :new, hash) %> + <%= gettext "Verify the contract " %><%= gettext "here" %> + <% _ -> %> + <%= nil %> + <% end %> +
    + <% _ -> %> + <%= nil %> + <% end %> +
    +
    <%= gettext "Transaction" %>
    +
    +

    + <%= link( + @log.transaction, + to: transaction_path(@conn, :show, @log.transaction), + "data-test": "log_address_link", + "data-address-hash": @log.transaction + ) %> +

    +
    + <%= case decoded_result do %> + <% {:error, :could_not_decode} -> %> +
    <%= gettext "Decoded" %>
    +
    +
    + <%= gettext "Failed to decode log data." %> +
    + <% {:ok, method_id, text, mapping} -> %> +
    <%= gettext "Decoded" %>
    +
    + + + + + + + + + +
    Method Id0x<%= method_id %>
    Call<%= text %>
    + <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> + <% {:error, :contract_not_verified, results} -> %> + <%= for {:ok, method_id, text, mapping} <- results do %> +
    <%= gettext "Decoded" %>
    +
    + + + + + + + + + +
    Method Id0x<%= method_id %>
    Call<%= text %>
    + <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> +
    + <% end %> + <% _ -> %> + <%= nil %> + <% end %> +
    <%= gettext "Topics" %>
    +
    +
    + <%= unless is_nil(@log.first_topic) do %> +
    + [0] + <%= @log.first_topic %> +
    + <% end %> + <%= unless is_nil(@log.second_topic) do %> +
    + [1] + <%= @log.second_topic %> +
    + <% end %> + <%= unless is_nil(@log.third_topic) do %> +
    + [2] + <%= @log.third_topic %> +
    + <% end %> + <%= unless is_nil(@log.fourth_topic) do %> +
    + [3] + <%= @log.fourth_topic %> +
    + <% end %> +
    +
    +
    + <%= gettext "Data" %> +
    +
    + <%= unless is_nil(@log.data) do %> +
    + <%= @log.data %> +
    + <% end %> +
    + +
    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 new file mode 100644 index 0000000..3dc20d3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex @@ -0,0 +1,46 @@ +
    + <% 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, tags: @tags %> +
    +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> + +
    +

    <%= gettext "Logs" %>

    +
    + + + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + + + +
    +
    + <%= gettext "There are no logs for this address." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "logs", conn: @conn %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + +
    + +
    + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..58ff9c9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex @@ -0,0 +1,58 @@ +
    + + <% 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, tags: @tags %> + +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> + <%= 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/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..a3577a7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex @@ -0,0 +1,17 @@ +
    + + <% 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, 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...") %> +
    +
    +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..7691bb3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex @@ -0,0 +1,58 @@ + + + + + <%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %> + <% chain_id_for_token_icon = Application.get_env(:block_scout_web, :chain_id) %> + <% address_hash = @token.contract_address_hash %> + <%= + render BlockScoutWeb.TokensView, + "_token_icon.html", + chain_id: chain_id_for_token_icon, + address: Address.checksum(address_hash) + %> + <% end %> + + + <%= link( + to: address_token_transfers_path(@conn, :index, to_string(@address.hash), to_string(@token.contract_address_hash)), + class: "tile-title-lg", + "data-test": "token_transfers_#{@token.contract_address_hash}" + ) do %> + <%= token_name(@token) %> + <% end %> + + + <%= @token.type %> + + + <%= format_according_to_decimals(@token_balance.value, @token.decimals) %> + + + <%= @token.symbol %> + + +

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

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

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

    + <% end %> + + + <%= with {:ok, address} <- Chain.hash_to_address(@token.contract_address_hash) do + render BlockScoutWeb.AddressView, + "_link.html", + address: address, + contract: false, + use_custom_tooltip: false, + no_tooltip: true + end + %> + + \ No newline at end of file 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 new file mode 100644 index 0000000..1fd8115 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -0,0 +1,75 @@ +
    + <% 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, tags: @tags %> + +
    +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + <%= render BlockScoutWeb.AddressTokenView, + "overview.html", + address: @address, + exchange_rate: @exchange_rate + %> + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + +
    + + + + + + + + + + + + + + + + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 9 %> + +
    +
     
    +
    +
     
    +
    +
    Asset
    +
    +
    Type
    +
    +
    Amount
    +
    +
    Symbol
    +
    +
    Price
    +
    +
    Value
    +
    +
    Contract Address
    +
    +
    + + + +
    +
    + <%= gettext "There are no tokens for this address." %> +
    +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex new file mode 100644 index 0000000..6f92237 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex @@ -0,0 +1,73 @@ +<% native_coin = Explorer.coin_name() %> + +<% wei_value = if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %> +<% raw_usd_value = + case @exchange_rate.usd_value do + %Decimal{} -> + if wei_value do + %Wei{value: Decimal.new(wei_value)} + |> Wei.to(:ether) + |> Decimal.mult(@exchange_rate.usd_value) + else + Decimal.new(0) + end + _ -> Decimal.new(0) + end +%> +<% data_usd_exchange_rate = + unless AddressView.empty_exchange_rate?(@exchange_rate) do + "data-usd-exchange-rate='#{@exchange_rate.usd_value}' data-raw-usd-value='#{raw_usd_value}'" + end +%> +<% native_coin_balance_token = AddressView.balance(@address) +%> +<% native_coin_balance_usd = + if AddressView.empty_exchange_rate?(@exchange_rate) do + nil + else + " + " + end +%> +<% native_coin_balance = + if native_coin_balance_usd do + native_coin_balance_usd <> " | " <> native_coin_balance_token + else + native_coin_balance_token + end +%> +
    + <%= render BlockScoutWeb.AddressTokenView, "overview_item.html", + title: gettext("Net Worth"), + tooltip: gettext("Shows total assets held in the address"), + value: "N/A", + data_test: "address-tokens-panel-net-worth", + classes: ["fs-14"] + %> + <%= render BlockScoutWeb.AddressTokenView, "overview_item.html", + title: "#{native_coin} #{gettext("Balance")}", + tooltip: "#{gettext("Shows the current")} #{native_coin} #{gettext("balance of the address")}", + value: raw(native_coin_balance), + data_test: "address-tokens-panel-native-worth", + classes: ["fs-14"] + %> + <%= render BlockScoutWeb.AddressTokenView, "overview_item.html", + title: gettext("Tokens"), + tooltip: gettext("Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)."), + value: "N/A", + data_test: "address-tokens-panel-tokens-worth", + classes: ["fs-14"] + %> + <%= render BlockScoutWeb.AddressTokenView, "overview_item.html", + title: gettext("CRC Worth"), + tooltip: gettext("Shows the total CRC balance in the address."), + value: "0 CRC", + data_test: "address-tokens-panel-crc-total-worth", + data_test_container: "address-tokens-panel-crc-total-worth-container", + classes: ["fs-14"], + container_classes: ["d-none"] + %> +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview_item.html.eex new file mode 100644 index 0000000..c038254 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview_item.html.eex @@ -0,0 +1,14 @@ +
    " data-test="<%= if assigns[:data_test_container], do: @data_test_container %>" style="padding: 10px;"> +
    +
    +

    <%= @title %>

    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip.html", + text: @tooltip, + additional_classes: ["ml-2"] + %> +
    +
    ""> + <%= @value %> +
    +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex new file mode 100644 index 0000000..9b3f6a7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex @@ -0,0 +1,78 @@ +
    + <%= if Enum.any?(@token_balances) do %> + +
    + (>) +
    + <%= if @conn.request_path |> String.contains?("/tokens") do %> + + + + + + <% else %> + + + + + + <% end %> + <% else %> + <%= tokens_count_title(@token_balances) %> + <% end %> + + +
    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 new file mode 100644 index 0000000..ceaf905 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex @@ -0,0 +1,65 @@ +
    + + + <%= for {token_balance, token} <- sort_by_usd_value_and_name(@token_balances) do %> +
    + <% path = cond do + 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( + to: path, + class: "dropdown-item" + ) do %> + + + + <% end %> +
    + <% end %> +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..94f202d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex @@ -0,0 +1,76 @@ +
    + <% 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, tags: @tags %> + +
    +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + + <%= if assigns[:token] do %> +

    + <%= gettext "Tokens" %> / <%= token_name(@token) %> +

    + <% end %> + + <%= if !assigns[:token] do %> +
    +

    <%= gettext "Token Transfers" %>

    +
    + + <%= 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 %> +
    +
    + <% end %> + + + + + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "token-transfers", conn: @conn %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + +
    +
    + + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..a5b5786 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -0,0 +1,72 @@ +
    + + <% 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, tags: @tags %> + +
    +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost, click to load newer transactions") %> +
    +

    <%= gettext "Transactions" %>

    +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    +
    + + + +
    +
    + <%= gettext "There are no transactions for this address." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "transactions", conn: @conn %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + +
    +
    + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_validation/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..e435195 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex @@ -0,0 +1,33 @@ +
    + <% 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, tags: @tags %> + +
    +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost, click to load newer validations") %> +

    <%=gettext("Blocks Validated")%>

    + + <%= 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 %> + + + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..7febeee --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex @@ -0,0 +1,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, tags: @tags %> + +
    + <%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %> +
    + <%= 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/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/_metatags.html.eex new file mode 100644 index 0000000..3ef2a67 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> 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 new file mode 100644 index 0000000..a3577a7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex @@ -0,0 +1,17 @@ +
    + + <% 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, 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...") %> +
    +
    +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/admin/dashboard/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/admin/dashboard/index.html.eex new file mode 100644 index 0000000..77140e5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/admin/dashboard/index.html.eex @@ -0,0 +1,37 @@ +
    +
    +
    +

    Tasks

    +
    + +
    +
    +
    +
    +
    +

    Create Contract Methods

    +
    +
    +

    + <%= gettext("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.") %> +

    +
    + + +
    +
    +
    +
    +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/admin/session/login_form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/admin/session/login_form.html.eex new file mode 100644 index 0000000..b2ed066 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/admin/session/login_form.html.eex @@ -0,0 +1,17 @@ +
    +
    +
    +
    +
    +

    Administrator Login

    + + <%= form_for @changeset, session_path(@conn, :create), [], fn f -> %> + <%= FormView.text_field(f, :username, :text, id: "username", required: true, label: "Username") %> + <%= FormView.text_field(f, :password, :password, id: "password", required: true, label: "Password") %> + + <% end %> +
    +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/admin/setup/admin_registration.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/admin/setup/admin_registration.html.eex new file mode 100644 index 0000000..6019a0a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/admin/setup/admin_registration.html.eex @@ -0,0 +1,19 @@ +
    +
    +
    +
    +
    +

    Administrator Setup

    + + <%= form_for @changeset, setup_path(@conn, :configure_admin, %{state: @conn.query_params["state"]}), [], fn f -> %> + <%= FormView.text_field(f, :username, :text, id: "username", required: true, label: "Username") %> + <%= FormView.text_field(f, :email, :email, id: "email", required: true, label: "Email Address") %> + <%= FormView.text_field(f, :password, :password, id: "password", required: true, label: "Password") %> + <%= FormView.text_field(f, :password_confirmation, :password, id: "password-confirmation", required: true, label: "Password Confirmation") %> + + <% end %> +
    +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/admin/setup/verify.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/admin/setup/verify.html.eex new file mode 100644 index 0000000..12ba89c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/admin/setup/verify.html.eex @@ -0,0 +1,25 @@ +
    +
    +
    +
    +
    +

    Administrator Setup

    + +

    You have not setup the administrator account for BlockScout. Run the following command in your terminal at the root directory of where your application is deployed. Paste the value inside of the Recovery Key field. Do not share this key!

    + +
    +
    pbcopy < apps/explorer/priv/.recovery
    +
    + <%= form_for @conn, setup_path(@conn, :configure_admin), [as: "verify"], fn f -> %> +
    + + <%= password_input(f, :recovery_key, class: "form-control", type: "password", id: "recovery-key", placeholder: "JRAJpuEGNKM1XQK3zpWMdAAHVzQtJfDyW/sN/Zn1Ev8=", required: true) %> +
    + + + <% end %> +
    +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/advertisement/banners_ad/_banner_728.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/banners_ad/_banner_728.html.eex new file mode 100644 index 0000000..20c47d2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/banners_ad/_banner_728.html.eex @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/advertisement/text_ad/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/text_ad/index.html.eex new file mode 100644 index 0000000..c8dc85c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/text_ad/index.html.eex @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex new file mode 100644 index 0000000..240294c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex @@ -0,0 +1,259 @@ +
    + + +
    +

    + <%= gettext "Parameters" %> + + +

    + +
    +
    +

    <%= gettext "Name" %>

    +

    <%= gettext "Description" %>

    +
    + +
    +
    +
    <%= gettext "Module" %> *<%= gettext "required" %>
    +

    <%= gettext "string" %> <%= gettext "(query)" %>

    +
    +
    +

    <%= gettext "A string with the name of the module to be invoked." %>

    +

    <%= gettext "Must be set to:" %> <%= @module_name %> +

    +
    + +
    +
    +
    <%= gettext "Action" %> *<%= gettext "required" %>
    +

    <%= gettext "string" %> <%= gettext "(query)" %>

    +
    +
    +

    <%= gettext "A string with the name of the action to be invoked." %>

    +

    <%= gettext "Must be set to:" %> <%= @action.name %>

    +
    +
    + + <%= for required_param <- @action.required_params do %> +
    +
    +
    <%= required_param.key %> *<%= gettext "required" %>
    +

    <%= required_param.type %> <%= gettext "(query)" %>

    +
    +
    +

    <%= required_param.description %>

    +
    + +
    +
    +
    + <% end %> + + <%= for optional_param <- @action.optional_params do %> +
    +
    +
    <%= optional_param.key %>
    +

    <%= optional_param.type %> <%= gettext "(query)" %>

    +
    +
    +

    <%= optional_param.description %>

    + +
    +
    + <% end %> + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    <%= gettext "Curl" %>
    +
    +
    
    +          
    +
    +
    +
    <%= gettext "Request URL" %>
    +
    +
    
    +          
    +
    +
    <%= gettext "Server Response" %>
    +
    +

    <%= gettext "Code" %>

    +

    <%= gettext "Details" %>

    +
    +
    +
    +
    +

    <%= gettext "Response Body" %>

    +
    +
    
    +            
    +
    +
    +
    +
    + +

    <%= gettext "Responses" %>

    +
    +

    <%= gettext "Code" %>

    +
    <%= gettext "Description" %>
    +
    + <%= for {response, index} <- Enum.with_index(@action.responses) do %> +
    +
    <%= response.code %>
    +
    +
    +
    <%= response.description %>
    +
    + + + +
    + +
    +
    +
    
    +            
    +
    + <%= if index == 0 do %> + +
    + <%= render "_model_table.html", model: response.model %> +
    + <% end %> +
    +
    +
    + <% end %> +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex new file mode 100644 index 0000000..3330a3a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex @@ -0,0 +1,182 @@ +
    +
    +
    +

    <%= @action %>

    +

    <%= raw @info.notes %>

    + + curl -X POST --data '{"id":0,"jsonrpc":"2.0","method": "<%= @action %>", "params": []}' + +

    +

    +
    
    +        
    +

    +
    + +
    + +
    +

    + <%= gettext "Parameters" %> + + +

    + +
    +
    +

    <%= gettext "Name" %>

    +

    <%= gettext "Description" %>

    +
    + + <%= for param <- @info.params do %> +
    +
    +
    + <%= param.name %> + <%= if param.required do %> + + *<%= gettext "required" %> + + <% end %> +
    +
    +
    +

    <%= param.description %>

    + " + data-parameter-type='<%= param.type %>' + data-required='<%= if param.required, do: "true", else: "false" %>' + data-selector='<%= "eth-#{@action}-try-api-ui" %>' + type="text", + value='<%= param.default %>' + /> +
    +
    + <% end %> + + +
    +
    + + +
    +
    + + +
    +
    +
    <%= gettext "Curl" %>
    +
    +
    
    +          
    +
    +
    <%= gettext "Server Response" %>
    +
    +

    <%= gettext "Code" %>

    +

    <%= gettext "Details" %>

    +
    +
    +
    +
    +

    <%= gettext "Response Body" %>

    +
    +
    
    +            
    +
    +
    +
    +
    + + +

    <%= gettext "Responses" %>

    +
    +

    <%= gettext "Code" %>

    +
    <%= gettext "Description" %>
    +
    +
    +
    200
    +
    +
    +
    successful operation
    +
    + + + +
    + +
    +
    +
    
    +            
    +
    +
    +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_metatags.html.eex new file mode 100644 index 0000000..9264738 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_metatags.html.eex @@ -0,0 +1,5 @@ + + <%= gettext "API for the %{subnetwork} - BlockScout", subnetwork: LayoutView.subnetwork_title() %> + +"> + 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 new file mode 100644 index 0000000..1452cc9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_model_table.html.eex @@ -0,0 +1,65 @@ +
    +

    <%= @model.name %> {

    + <%= for {key, details} <- @model.fields do %> +
    +
    + <%= key %> +
    +
    + + <%= if details[:type] != "model", do: details.type %> + <%= if details[:definition] do %> + + <% end %> + + <%= if details[:type] == "model" do %> + + <%= details.model.name %> + + <% end %> + <%= if details[:type] == "array" do %> +
    + [<%= details.array_type.name %>] +
    + <% end %> + <%= if details[:enum] do %> +
    enum: <%= details.enum %>
    +
    +
    enum +
    interpretation +
    + <%= for {enum, interpretation} <- details[:enum_interpretation] do %> +
    +
    + "<%= enum %>" +
    +
    + <%= interpretation %> +
    +
    + <% end %> + <% end %> + <%= if details[:example] do %> +
    example: <%= details.example %>
    + <% end %> + <%= if details[:description] do %> +
    description: <%= details.description %>
    + <% end %> +
    + +
    + <% end %> +

    }

    +
    + +<%= if @model.fields[:result][:type] == "array" do %> + <%= render "_model_table.html", model: @model.fields[:result].array_type %> +<% end %> + +<%= if @model.fields[:result][:type] == "model" do %> + <%= render "_model_table.html", model: @model.fields[:result].model %> +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_module_card.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_module_card.html.eex new file mode 100644 index 0000000..5def65b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_module_card.html.eex @@ -0,0 +1,12 @@ +
    +
    +

    + <%= "#{String.capitalize(@module.name)}" %> + ?module=<%= @module.name %> +

    +
    + <%= for action <- @module.actions do %> + <%= render "_action_tile.html", module_name: @module.name, action: action %> + <% end %> +
    + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_module_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_module_item.html.eex new file mode 100644 index 0000000..2224c2a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_module_item.html.eex @@ -0,0 +1,4 @@ + + <%= "#{String.capitalize(@module.name)}" %> + ?module=<%= @module.name %> + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex new file mode 100644 index 0000000..ac80b13 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex @@ -0,0 +1,25 @@ +
    +
    +
    +

    <%= gettext("ETH RPC API Documentation") %>

    +

    [ <%= gettext "Base URL:" %> <%= eth_rpc_api_url()%> ]

    +

    + <%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> + + <%= gettext "here." %> + <%= gettext "This is useful to allow sending requests to blockscout without having to change anything about the request." %> + <%= gettext "However, in general, the" %> <%= link( + gettext("custom RPC"), + to: api_docs_path(@conn, :index) + ) %> <%= gettext " is recommended." %> + <%= gettext "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." %> +

    +
    +
    +
    + <%= for {method, info} <- Map.to_list(@documentation) do %> + <%= render "_eth_rpc_item.html", action: method, info: info %> + <% end %> +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex new file mode 100644 index 0000000..6f27e0b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex @@ -0,0 +1,18 @@ +
    +
    +
    +

    <%= gettext("API Documentation") %>

    +

    [ <%= gettext "Base URL:" %> <%= api_url()%> ]

    +

    <%= gettext "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." %>

    +
    +
    + <%= for module <- @documentation do %> + <%= render "_module_item.html", module: module %> + <% end %> +
    +
    + <%= for module <- @documentation do %> + <%= render "_module_card.html", module: module %> + <% end %> + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex new file mode 100644 index 0000000..d910c2a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex @@ -0,0 +1,4 @@ +<%= link( + gettext("Block #%{number}", number: to_string(@block.number)), + to: block_path(BlockScoutWeb.Endpoint, :show, @block) +) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_metatags.html.eex new file mode 100644 index 0000000..c83fffd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_metatags.html.eex @@ -0,0 +1,13 @@ +<%= if assigns[:block] do %> + + <%= gettext( + "Block %{block_number} - %{subnetwork} Explorer", + block_number: @block.number, + subnetwork: BlockScoutWeb.LayoutView.subnetwork_title() + ) %> + + "> + "> +<% else %> + <%= BlockScoutWeb.LayoutView.render("_default_title.html") %> +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex new file mode 100644 index 0000000..f41857e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex @@ -0,0 +1,82 @@ +<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %> +
    +
    +
    + <%= if @block_type == "Block" do %> + <%= link( + class: "tile-label", + to: block_path(BlockScoutWeb.Endpoint, :show, @block), + "data-selector": "block-number" + ) do %> + #<%= @block %> + <% end %> + <% else %> + <%= link( + class: "tile-label", + to: block_path(BlockScoutWeb.Endpoint, :show, @block.hash), + "data-selector": "block-number" + ) do %> + #<%= @block %> + <% end %> + <% end %> + <%= @block_type %> +
    +
    +
    + + + <%= ngettext("%{count} transaction", "%{count} transactions", Enum.count(@block.transactions)) %> + + <%= if @block.size do %> + + <%= Cldr.Unit.new!(:byte, @block.size) |> cldr_unit_to_string!() %> + <% end %> + + +
    + <%= if System.get_env("HIDE_BLOCK_MINER") !== "true" do %> +
    + + <%= gettext "Miner" %> + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: @block.miner, + contract: false, + use_custom_tooltip: false %> +
    + <% end %> + <%= if show_reward?(@block.rewards) do %> +
    + + <%= gettext "Reward" %> + + <%= combined_rewards_value(@block) %> + +
    + <% end %> +
    +
    + <%= if !is_nil(@block.base_fee_per_gas) do %> + + <%= format_wei_value(%Wei{value: priority_fee}, :ether) %> <%= gettext "Priority Fees" %> + + <%= format_wei_value(burned_fee, :ether) %> <%= gettext "Burnt Fees" %> + <% end %> + + <%= formatted_gas(@block.gas_limit) %> <%= gettext "Gas Limit" %> + +
    + <%= formatted_gas(@block.gas_used) %> + <% gas = if Decimal.compare(@block.gas_limit, 0) == :gt, do: Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit), else: 0 %> + (<%= formatted_gas(gas, format: "#.#%") %>) + <%= gettext "Gas Used" %> +
    + +
    +
    ;" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"> +
    +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex new file mode 100644 index 0000000..dcee144 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex @@ -0,0 +1,25 @@ +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost, click to load newer blocks") %> + +

    <%= gettext("%{block_type}s", block_type: @block_type) %>

    + +
    + <%= 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 %> +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    +
    + <%= gettext "There are no blocks." %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex new file mode 100644 index 0000000..500e35a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex @@ -0,0 +1,271 @@ +<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %> +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    + +
    +
    +
    +

    + <%= gettext("%{block_type} Details", block_type: block_type(@block)) %> +

    + +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The block height of a particular block is defined as the number of blocks preceding it in the blockchain.") %> + <%= if block_type(@block) == "Block" do %> + <%= gettext("Block Height") %> + <% else %> + <%= gettext("%{block_type} Height", block_type: block_type(@block)) %> + <% end %> +
    +
    + <%= if block_type(@block) == "Block" do %> + <%= @block.number %> <%= if @block.number == 0, do: " - " <> gettext("Genesis Block")%> + <% else %> + <%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %> + <% end %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Date & time at which block was produced.") %> + <%= gettext("Timestamp") %> +
    +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The number of transactions in the block.") %> + <%= gettext("Transactions") %> +
    +
    + + <%= if @block_transaction_count == 1 do %> + <%= gettext "%{count} Transaction", count: @block_transaction_count %> + <% else %> + <%= gettext "%{count} Transactions", count: @block_transaction_count %> + <% end %> + +
    +
    + + <%= if System.get_env("HIDE_BLOCK_MINER") !== "true" 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: @block.miner, contract: false, class: "", use_custom_tooltip: false, show_full_hash: true %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], + clipboard_text: @block.miner, + aria_label: gettext("Copy Address"), + title: gettext("Copy Address") %> +
    +
    + <% end %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + 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") %>
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The SHA256 hash of the block.") %> + <%= gettext("Hash") %> +
    +
    <%= to_string(@block.hash) %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], + clipboard_text: to_string(@block.hash), + aria_label: gettext("Copy Hash"), + title: gettext("Copy Hash") %> +
    +
    + <%= unless @block.number == 0 do %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The hash of the block from which this block was generated.") %> + <%= gettext("Parent Hash") %> +
    +
    <%= link( + @block.parent_hash, + class: "transaction__link", + to: block_path(@conn, :show, @block.number - 1) + ) %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], + clipboard_text: to_string(@block.parent_hash), + aria_label: gettext("Copy Parent Hash"), + title: gettext("Copy Parent Hash") %> +
    +
    + <% end %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks).") %> + <%= gettext("Difficulty") %> +
    +
    <%= @block.difficulty |> Decimal.to_integer() |> BlockScoutWeb.Cldr.Number.to_string! %>
    +
    + <%= if block_type(@block) == "Block" do %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Total difficulty of the chain until this block.") %> + <%= gettext("Total Difficulty") %> +
    +
    <%= @block.total_difficulty |> Decimal.to_integer() |> BlockScoutWeb.Cldr.Number.to_string! %>
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The total gas amount used in the block and its percentage of gas filled in the block.") %> + <%= gettext("Gas Used") %> +
    +
    <%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %> | <%= if Decimal.compare(@block.gas_limit, 0) == :eq, do: "0%", else: ((Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%")) %>
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Total gas limit provided by all transactions in the block.") %> + <%= gettext("Gas Limit") %> +
    +
    <%= BlockScoutWeb.Cldr.Number.to_string!(@block.gas_limit) %>
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("64-bit hash of value verifying proof-of-work (note: null for POA chains).") %> + <%= gettext("Nonce") %> +
    +
    <%= to_string(@block.nonce) %>
    +
    + <% end %> + <%= if !is_nil(@block.base_fee_per_gas) do%> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Minimum fee required per unit of gas. Fee adjusts based on network congestion.") %> + <%= gettext("Base Fee per Gas") %> +
    +
    <%= format_wei_value(@block.base_fee_per_gas, :gwei) %>
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + 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) %>
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("User-defined tips sent to validator for transaction priority/inclusion.") %> + <%= gettext("Priority Fee / Tip") %> +
    +
    <%= format_wei_value(%Wei{value: priority_fee}, :ether) %>
    +
    + <% end %> + <%= if show_reward?(@block.rewards) do %> +
    + <%= for block_reward <- @block.rewards do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees.") %> + <%= block_reward_text(block_reward, @block.miner.hash) %> +
    +
    <%= format_wei_value(block_reward.reward, :ether) %>
    +
    + <% end %> + <% end %> + <%= if block_type(@block) == "Block" do %> + <%= if length(@block.uncle_relations) > 0 do %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Index position(s) of referenced stale blocks.") %> + <%= gettext("Uncles") %> +
    +
    <%= for {relation, index} <- Enum.with_index(@block.uncle_relations) do %> + <%= link( + gettext("Position %{index}", index: index), + class: "transaction__link", + "data-toggle": "tooltip", + "data-placement": "top" , + "data-test": "uncle_link", + "data-uncle-hash": to_string(relation.uncle_hash), + to: block_path(@conn, :show, relation.uncle_hash) + ) %><%= if index < length(@block.uncle_relations) - 1 do %>,<% end %> + <% end %>
    +
    + <% end %> + <% end %> +
    +
    +
    +
    + +<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/404.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/404.html.eex new file mode 100644 index 0000000..5f87d8d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/404.html.eex @@ -0,0 +1,13 @@ +
    +
    +
    + Block Not Found +
    +
    +

    <%= gettext("Block Details") %>

    +

    <%= block_not_found_message(@block_above_tip) %>

    + Back Home +
    +
    +
    + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/_metatags.html.eex new file mode 100644 index 0000000..bff7f94 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/_metatags.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.BlockView, "_metatags.html", conn: @conn, block: @block %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex new file mode 100644 index 0000000..c3f9ab4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex @@ -0,0 +1,41 @@ +
    + + <%= render BlockScoutWeb.BlockView, "overview.html", assigns %> + +
    +
    +
    + <%= + link( + gettext("Transactions"), + class: "card-tab #{tab_status("transactions", @conn.request_path)}", + to: block_transaction_path(@conn, :index, @conn.params["block_hash_or_number"]) + ) + %> +
    + +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + + + +
    +
    + <%= gettext "There are no transactions for this block." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    + + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex new file mode 100644 index 0000000..e1551fd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex @@ -0,0 +1,32 @@ +
    +
    + <%= link( + @block, + class: "tile-title", + to: block_path(BlockScoutWeb.Endpoint, :show, @block), + "data-selector": "block-number" + ) %> +
    +
    + <%= gettext("%{count} Transactions", count: Enum.count(@block.transactions)) %> + +
    + <%= if System.get_env("HIDE_BLOCK_MINER") !== "true" do %> +
    + <%= gettext "Miner" %> + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: @block.miner, + contract: false, + use_custom_tooltip: false, + custom_classes_tooltip: ["miner-address-tooltip"] %> +
    + <% end %> + <%= if BlockScoutWeb.BlockView.show_reward?(@block.rewards) do %> +
    + <%= gettext "Reward" %> <%= BlockScoutWeb.BlockView.combined_rewards_value(@block) %> +
    + <% end %> +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/_metatags.html.eex new file mode 100644 index 0000000..a46a91b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/_metatags.html.eex @@ -0,0 +1,5 @@ + + <%= gettext("%{subnetwork} %{network} Explorer", subnetwork: LayoutView.subnetwork_title(), network: LayoutView.network_title()) %> + +"> + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex new file mode 100644 index 0000000..4b84dce --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex @@ -0,0 +1,43 @@ +
    + + <%= gettext "Gas tracker" %> + +
    + <% gas_prices_from_oracle = gas_prices() %> + <%= if gas_prices_from_oracle do %> + +
    +
    +
    <%= "#{gas_prices_from_oracle["average"]}" <> " " %><%= gettext "Gwei" %>
    +
    +
    +
    <%= gettext "Slow" %><%= gas_prices_from_oracle["slow"] %> <%= gettext "Gwei" %>
    +
    <%= gettext "Average" %><%= gas_prices_from_oracle["average"] %> <%= gettext "Gwei" %>
    +
    <%= gettext "Fast" %><%= gas_prices_from_oracle["fast"] %> <%= gettext "Gwei" %>
    +
    + " + > + + +
    +
    + + <% else %> + <%= if @gas_price do %> + + + <%= render BlockScoutWeb.IconsView, "_gas_price_icon.html" %> + + <%= @gas_price <> " " %> + <%= gettext "Gwei" %> + + <% end %> + <% end %> +
    +
    \ No newline at end of file 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 new file mode 100644 index 0000000..07e77eb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -0,0 +1,231 @@ +
    +
    +
    + +
    + +
    + + + "<%= key %>":"<%= value %>" + <%= if x<(map_size(@chart_data_paths)-1) do %> + , + <% end %> + <% end %>}' + data-history_chart_config = '<%= @chart_config_json %>' + width="350" height="152"> + +
    + + +
    + <%= if Map.has_key?(@chart_config, :market) do %> + + <%# THE FOLLOWING LINE PREVENTS COPY/PASTE ERRORS %> + <%# Explicity put @chart_config.market in a variable %> + <%# This is done so that when people add a new chart source, x, %> + <%# They wont just access @chart_config.x w/o first checking if x exists%> + <% market_chart_config = @chart_config.market%> + + <%= if Enum.member?(market_chart_config, :price) do %> +
    + + <%= Explorer.coin_name() %> <%= gettext "Price" %> + +
    + + +
    +
    + <% end %> + <%= if Enum.member?(@chart_config.market, :market_cap) do %> +
    + + <%= gettext "Market Cap" %> + +
    + <% total_market_cap = market_cap(@market_cap_calculation, @exchange_rate) %> + + +
    +
    + <% end %> + <% end %> + <%= render BlockScoutWeb.ChainView, "gas_price_oracle_legend_item.html", gas_price: @gas_price %> + <%= if Map.has_key?(@chart_config, :transactions) do %> + + <% transaction_chart_config = @chart_config.transactions%> + <%= if Enum.member?(transaction_chart_config, :transactions_per_day) do %> +
    + + <%= gettext "Daily Transactions" %> + + + <% num_of_transactions = BlockScoutWeb.Cldr.Number.to_string!(Enum.at(@transaction_stats, 0).number_of_transactions, format: "#,###") %> + <%= num_of_transactions %> + <% gas_used = Enum.at(@transaction_stats, 0).gas_used %> + <%= if gas_used && gas_used > 0 do %> +
    "> + + + <% end %> + +
    + <% end %> + <% end %> +
    +
    + +
    +
    + <%= case @average_block_time do %> + <% {:error, :disabled} -> %> + <%= nil %> + <% average_block_time -> %> +
    + + <%= gettext "Average block time" %> + + + <%= Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat) %> + +
    + <% end %> +
    + + <%= gettext "Total transactions" %> + +
    + + <%= BlockScoutWeb.Cldr.Number.to_string!(@transaction_estimated_count, format: "#,###") %> + + <%= if @total_gas_usage > 0 do %> +
    " + class="custom-tooltip-total-transactions"> + + + <% end %> +
    +
    +
    + + <%= gettext "Total blocks" %> + + + <%= BlockScoutWeb.Cldr.Number.to_string!(@block_count, format: "#,###") %> + +
    +
    + + <%= gettext "Wallet addresses" %> + + + <%= BlockScoutWeb.Cldr.Number.to_string!(@address_count, format: "#,###") %> + +
    +
    +
    +
    +
    + +
    +
    +
    + <%= link(gettext("View All Blocks"), to: blocks_path(BlockScoutWeb.Endpoint, :index), class: "btn-line float-right") %> +

    <%= gettext "Blocks" %>

    +
    + + + + + +
    +
    +
    + + <%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> + +
    +
    + <%= link(gettext("View All Transactions"), to: transaction_path(BlockScoutWeb.Endpoint, :index), class: "btn-line float-right") %> +

    <%= gettext "Transactions" %>

    + + + + + +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_add_full.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_add_full.html.eex new file mode 100644 index 0000000..deae2f8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_add_full.html.eex @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_add_line.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_add_line.html.eex new file mode 100644 index 0000000..586d5da --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_add_line.html.eex @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy.html.eex new file mode 100644 index 0000000..d36e97f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy.html.eex @@ -0,0 +1,14 @@ + " + data-placement='top' + data-toggle='tooltip' + title='<%= @title %>' + style='<%= if assigns[:style] do @style end %>' + > + + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy_for_table.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy_for_table.html.eex new file mode 100644 index 0000000..486ef4f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy_for_table.html.eex @@ -0,0 +1,15 @@ + " + style='<%= if assigns[:style] do @style end %>' + > + + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_external_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_external_link.html.eex new file mode 100644 index 0000000..45081b8 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_external_link.html.eex @@ -0,0 +1,8 @@ + + <%= render BlockScoutWeb.IconsView, "_external_link.html" %> + <%= @text %> + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_line.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_line.html.eex new file mode 100644 index 0000000..4c1444c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_line.html.eex @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex new file mode 100644 index 0000000..183a55c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex new file mode 100644 index 0000000..665733f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex @@ -0,0 +1,4 @@ +
    + <%= render BlockScoutWeb.CommonComponentsView, "_info.html" %> + <%= gettext("Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.") %> +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_channel_disconnected_message.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_channel_disconnected_message.html.eex new file mode 100644 index 0000000..4dd8e27 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_channel_disconnected_message.html.eex @@ -0,0 +1,5 @@ +
    +
    + <%= @text %> +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_check_tooltip.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_check_tooltip.html.eex new file mode 100644 index 0000000..7b7b1de --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_check_tooltip.html.eex @@ -0,0 +1,14 @@ +
    + + + + +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex new file mode 100644 index 0000000..9fd1933 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip.html.eex new file mode 100644 index 0000000..b72928c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip.html.eex @@ -0,0 +1,14 @@ +
    " + data-boundary="window" + data-container="body" + data-html="true" + data-placement="top" + data-toggle="tooltip" + title="<%= @text %>" +> + " height="<%= if assigns[:height] do @height else "16" end %>"> + + + +
    \ No newline at end of file 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 new file mode 100644 index 0000000..87a55cf --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_i_tooltip_2.html.eex @@ -0,0 +1,11 @@ +" + data-boundary="window" + data-container="body" + data-html="true" + data-placement="top" + 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/_icon_error_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_error_modal.html.eex new file mode 100644 index 0000000..8e1242d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_error_modal.html.eex @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_question_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_question_modal.html.eex new file mode 100644 index 0000000..14c5089 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_question_modal.html.eex @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_success_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_success_modal.html.eex new file mode 100644 index 0000000..71779f2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_success_modal.html.eex @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_warning_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_warning_modal.html.eex new file mode 100644 index 0000000..a8b4011 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_icon_warning_modal.html.eex @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_info.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_info.html.eex new file mode 100644 index 0000000..0ab5088 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_info.html.eex @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_input_group.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_input_group.html.eex new file mode 100644 index 0000000..5c9c929 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_input_group.html.eex @@ -0,0 +1,17 @@ +
    + + type="<%= if assigns[:type] do @type end %>" + class="<%= if assigns[:input_classes] do @input_classes end %>" + placeholder="<%= if assigns[:placeholder] do @placeholder end %>" + value="<%= if assigns[:value] do @value end %>" + <%= if assigns[:disabled] do "disabled" end %> + /> + <%= if assigns[:prepend] do %> +
    +
    <%= @prepend %>
    +
    + <% end %> +
    <%= if assigns[:message] do @message end %>
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_loading_spinner.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_loading_spinner.html.eex new file mode 100644 index 0000000..d501012 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_loading_spinner.html.eex @@ -0,0 +1,6 @@ + + + + +<%= if assigns[:loading_text], do: @loading_text %> + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex new file mode 100644 index 0000000..c3f069b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex @@ -0,0 +1,10 @@ +
    + <%= render BlockScoutWeb.CommonComponentsView, "_info.html" %> + <%= gettext("Minimal Proxy Contract for") %> <%= link( +@address_hash, +to: address_contract_path(@conn, :index, @address_hash)) %>.
    <%= link( + gettext("EIP-1167"), + to: "https://eips.ethereum.org/EIPS/eip-1167", + target: "_blank" +) %><%= gettext(" - minimal bytecode implementation that delegates all calls to a known address") %>
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_bottom_disclaimer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_bottom_disclaimer.html.eex new file mode 100644 index 0000000..2846db4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_bottom_disclaimer.html.eex @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_close_button.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_close_button.html.eex new file mode 100644 index 0000000..681d1c4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_close_button.html.eex @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex new file mode 100644 index 0000000..e95d088 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_status.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_status.html.eex new file mode 100644 index 0000000..afb09a4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_status.html.eex @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex new file mode 100644 index 0000000..7fd9f01 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex @@ -0,0 +1,66 @@ +
    + <%= if false do %> + +
    + <%= gettext "Show" %> + + <%= gettext "Records" %> +
    + <% end %> + + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_progress_from_to.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_progress_from_to.html.eex new file mode 100644 index 0000000..4ae44e5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_progress_from_to.html.eex @@ -0,0 +1,9 @@ +
    +
    +
    <%= @from %>
    +
    <%= @to %>
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex new file mode 100644 index 0000000..efd1ce6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex @@ -0,0 +1,15 @@ +
    +
      +
    +
      + + +
    • +
    +
    +<%= if assigns[:showing_limit] do %> +
    (<%= gettext("Only the first")%> <%= assigns[:showing_limit] |> BlockScoutWeb.Cldr.Number.to_string! %> <%= gettext("elements are displayed")%>) +
    +<% end %> \ 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 new file mode 100644 index 0000000..bda361e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_status_icon.html.eex @@ -0,0 +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 0000000..1c08c85 --- /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 0000000..559468d --- /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 0000000..4f93df8 --- /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 0000000..7d83186 --- /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/common_components/_table-loader.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex new file mode 100644 index 0000000..ecaf52d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex @@ -0,0 +1,9 @@ +<%= for _r <- 1..5 do %> + + <%= for _c <- 1..@columns_num do %> + + + + <% end %> + +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tenderly_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tenderly_link.html.eex new file mode 100644 index 0000000..665a567 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tenderly_link.html.eex @@ -0,0 +1,4 @@ +<% tenderly_link = "https://dashboard.tenderly.co/tx#{@tenderly_chain_path}/" <> "0x" <> Base.encode16(@transaction_hash.bytes, case: :lower) %> + + Open in Tenderly <%= render BlockScoutWeb.IconsView, "_external_link.html" %> + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex new file mode 100644 index 0000000..91195d5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex @@ -0,0 +1,120 @@ +
    +
    +
    + + + + + + +
    +
    + + +
    +
    + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + +
    +
    + + +
    +
    + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + +
    +
    + + +
    +
    + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + +
    +
    + + +
    +
    + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + +
    +
    + + +
    +
    + + + + + + +
    +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex new file mode 100644 index 0000000..d79aff6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex @@ -0,0 +1,12 @@ +<%= case @type do %> + <% :token_burning -> %> + <%= gettext("Token Burning") %> + <% :token_minting -> %> + <%= gettext("Token Minting") %> + <% :token_spawning -> %> + <%= gettext("Token Creation") %> + <% :token_transfer -> %> + <%= gettext("Token Transfer") %> + <% _ -> %> + <%= gettext("Token Transfer") %> +<% end %> \ 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 new file mode 100644 index 0000000..98293cc --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex @@ -0,0 +1,52 @@ +<%= + for status <- ["error", "warning", "success", "question"] do + render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status + end + %> +"> +
    +
    +
    +

    <%= gettext "Export Data" %>

    + +
    +

    Export <%= type_display_name(@type) %> for address <%= link( + @address_hash_string, + to: address_path(@conn, :show, @address_hash_string) + ) %> to CSV file

    +
    + +
    + from to +
    + +
    + + +
    +
    + + + +
    \ 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 new file mode 100644 index 0000000..bbf314d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/error422/index.html.eex @@ -0,0 +1,12 @@ +
    +
    +
    + Page Not Found +
    +
    +

    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 +
    +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/form/_tag.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/form/_tag.html.eex new file mode 100644 index 0000000..54f032d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/form/_tag.html.eex @@ -0,0 +1,3 @@ +
    "> + <%= @text %> +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/form/text_field.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/form/text_field.html.eex new file mode 100644 index 0000000..c0a307c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/form/text_field.html.eex @@ -0,0 +1,15 @@ +
    + <%= if @label do %> + <%= if @id do %> + + <% else %> + + <% end %> + <% end %> + <%= @input_field %> + <%= for error <- @errors do %> +
    + <%= error %> +
    + <% end %> +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_accounts_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_accounts_icon.html.eex new file mode 100644 index 0000000..9b30e9e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_accounts_icon.html.eex @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_active_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_active_icon.html.eex new file mode 100644 index 0000000..6da126f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_active_icon.html.eex @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_api_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_api_icon.html.eex new file mode 100644 index 0000000..5f7da6f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_api_icon.html.eex @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_apps_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_apps_icon.html.eex new file mode 100644 index 0000000..af60841 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_apps_icon.html.eex @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_block_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_block_icon.html.eex new file mode 100644 index 0000000..fa782fe --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_block_icon.html.eex @@ -0,0 +1,5 @@ + + + + + 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 0000000..f823473 --- /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/icons/_check_dark_forest_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_check_dark_forest_icon.html.eex new file mode 100644 index 0000000..56a4854 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_check_dark_forest_icon.html.eex @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_external_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_external_link.html.eex new file mode 100644 index 0000000..5f9f06e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_external_link.html.eex @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_gas_price_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_gas_price_icon.html.eex new file mode 100644 index 0000000..01cdad2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_gas_price_icon.html.eex @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_guage_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_guage_icon.html.eex new file mode 100644 index 0000000..e5cae29 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_guage_icon.html.eex @@ -0,0 +1,18 @@ + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_hourglass_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_hourglass_icon.html.eex new file mode 100644 index 0000000..761b37c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_hourglass_icon.html.eex @@ -0,0 +1,17 @@ + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_inactive_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_inactive_icon.html.eex new file mode 100644 index 0000000..93b7a65 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_inactive_icon.html.eex @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_network_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_network_icon.html.eex new file mode 100644 index 0000000..cf668d0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_network_icon.html.eex @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_search_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_search_icon.html.eex new file mode 100644 index 0000000..b93ed63 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_search_icon.html.eex @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_smart_contract.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_smart_contract.html.eex new file mode 100644 index 0000000..372858f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_smart_contract.html.eex @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_test_network_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_test_network_icon.html.eex new file mode 100644 index 0000000..111fc1c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_test_network_icon.html.eex @@ -0,0 +1,3 @@ + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_tokens_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_tokens_icon.html.eex new file mode 100644 index 0000000..6d808a1 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_tokens_icon.html.eex @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_transaction_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_transaction_icon.html.eex new file mode 100644 index 0000000..179613d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_transaction_icon.html.eex @@ -0,0 +1,4 @@ + + + + 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 new file mode 100644 index 0000000..d88a6d9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex @@ -0,0 +1,48 @@ +<% error = @internal_transaction.error %> +
    " data-test="internal_transaction" data-key="<%= @internal_transaction.transaction_hash %>_<%= @internal_transaction.index %>" data-internal-transaction-transaction-hash="<%= @internal_transaction.transaction_hash %>" data-internal-transaction-index="<%= @internal_transaction.index %>"> +
    + +
    + + <%= gettext("Internal Transaction") %> + + <%= type(@internal_transaction) %> + <%= if error do %> + <%= gettext "Error" %>: <%= error %> + <% end %> +
    + +
    + <%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @internal_transaction.transaction_hash %> + + <%= @internal_transaction |> BlockScoutWeb.AddressView.address_partial_selector(:from, assigns[:current_address]) |> (&(if is_list(&1), do: Keyword.put(&1, :ignore_implementation_name, true), else: &1)).() |> BlockScoutWeb.RenderHelpers.render_partial() %> + → + <%= @internal_transaction |> BlockScoutWeb.AddressView.address_partial_selector(:to, assigns[:current_address]) |> (&(if is_list(&1), do: Keyword.put(&1, :ignore_implementation_name, true), else: &1)).() |> BlockScoutWeb.RenderHelpers.render_partial() %> + + + + <%= BlockScoutWeb.TransactionView.value(@internal_transaction, include_label: false) %> <%= Explorer.coin_name() %> + + +
    + +
    + + <%= link( + gettext("Block #%{number}", number: to_string(@internal_transaction.block_number)), + to: block_path(BlockScoutWeb.Endpoint, :show, @internal_transaction.block_number) + ) %> + + + <%= if assigns[:current_address] do %> + + <%= if assigns[:current_address].hash == @internal_transaction.from_address_hash do %> + <%= gettext "OUT" %> + <% else %> + <%= gettext "IN" %> + <% end %> + + <% end %> +
    +
    +
    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 0000000..e8f8e58 --- /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 0000000..0e777ee --- /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/_default_title.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_default_title.html.eex new file mode 100644 index 0000000..38affa0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_default_title.html.eex @@ -0,0 +1,3 @@ + + <%= gettext("%{subnetwork} Explorer - BlockScout", subnetwork: subnetwork_title()) %> + 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 new file mode 100644 index 0000000..43b26a2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex @@ -0,0 +1,58 @@ +
    +
    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 new file mode 100644 index 0000000..18dbd03 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_search.html.eex @@ -0,0 +1,35 @@ + +
    "> +
    +
    "> + " + type="text" + tabindex="1" + > +
    +
    + +
    +
    +
    + / +
    +
    +
    + +
    \ No newline at end of file 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 new file mode 100644 index 0000000..297a834 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -0,0 +1,172 @@ +<% 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() %> + 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 new file mode 100644 index 0000000..2c578cf --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -0,0 +1,308 @@ + + + + + + + + <%= case @view_module do %> + <% Elixir.BlockScoutWeb.ChainView -> %> + "> + " as="script"> + " as="script"> + " as="script"> + <% _ -> %> + "> + <% end %> + " as="script"> + " as="image" crossorigin> + " as="image" crossorigin> + " as="image" crossorigin> + " as="image" crossorigin> + " as="image" crossorigin> + " as="image" crossorigin> + " as="style" onload="this.onload=null;this.rel='stylesheet'"> + "> + <%= render_existing(@view_module, "styles.html", assigns) %> + + "> + "> + "> + "> + " color="#5bbad5"> + "> + + "> + + + <%= render_existing(@view_module, "_metatags.html", assigns) || render("_default_title.html") %> + + + + + + + + + <% raw_dark_forest_addresses_0_4 = CustomContractsHelpers.get_raw_custom_addresses_list(:dark_forest_addresses) || "" %> + <% raw_dark_forest_addresses_0_5 = CustomContractsHelpers.get_raw_custom_addresses_list(:dark_forest_addresses_v_0_5) || "" %> + <% raw_dark_forest_addresses_0_6 = CustomContractsHelpers.get_raw_custom_addresses_list(:dark_forest_addresses_v_0_6) || "" %> + <% raw_dark_forest_addresses_0_6_r2 = CustomContractsHelpers.get_raw_custom_addresses_list(:dark_forest_addresses_v_0_6_r2) || "" %> + <% raw_dark_forest_addresses = raw_dark_forest_addresses_0_4 %> + <% raw_dark_forest_addresses = if raw_dark_forest_addresses_0_5 !== "", do: raw_dark_forest_addresses <> "," <> raw_dark_forest_addresses_0_5, else: raw_dark_forest_addresses %> + <% raw_dark_forest_addresses = if raw_dark_forest_addresses_0_6 !== "", do: raw_dark_forest_addresses <> "," <> raw_dark_forest_addresses_0_6, else: raw_dark_forest_addresses %> + <% raw_dark_forest_addresses = if raw_dark_forest_addresses_0_6_r2 !== "", do: raw_dark_forest_addresses <> "," <> raw_dark_forest_addresses_0_6_r2, else: raw_dark_forest_addresses %> + + <% raw_circles_addresses = CustomContractsHelpers.get_raw_custom_addresses_list(:circles_addresses) %> + <%= cond do %> + <% ( + @view_module == Elixir.BlockScoutWeb.TransactionInternalTransactionView || + @view_module == Elixir.BlockScoutWeb.TransactionLogView || + @view_module == Elixir.BlockScoutWeb.TransactionRawTraceView || + @view_module == Elixir.BlockScoutWeb.TransactionTokenTransferView || + @view_module == Elixir.BlockScoutWeb.TransactionStateView + ) -> %> + <% to_address = @transaction && @transaction.to_address && "0x" <> Base.encode16(@transaction.to_address.hash.bytes, case: :lower) %> + <% {:ok, created_from_address} = if @transaction.to_address_hash, do: Chain.hash_to_address(@transaction.to_address_hash), else: {:ok, nil} %> + <% created_from_address_hash_str = if from_address_hash(created_from_address), do: "0x" <> Base.encode16(from_address_hash(created_from_address).bytes, case: :lower), else: nil %> + + <% ( + @view_module == Elixir.BlockScoutWeb.AddressTransactionView || + @view_module == Elixir.BlockScoutWeb.AddressTokenTransferView || + @view_module == Elixir.BlockScoutWeb.AddressTokenView || + @view_module == Elixir.BlockScoutWeb.AddressInternalTransactionView || + @view_module == Elixir.BlockScoutWeb.AddressCoinBalanceView || + @view_module == Elixir.BlockScoutWeb.AddressLogsView || + @view_module == Elixir.BlockScoutWeb.AddressValidationView || + @view_module == Elixir.BlockScoutWeb.AddressContractView || + @view_module == Elixir.BlockScoutWeb.AddressReadContractView || + @view_module == Elixir.BlockScoutWeb.AddressReadProxyView || + @view_module == Elixir.BlockScoutWeb.AddressWriteContractView || + @view_module == Elixir.BlockScoutWeb.AddressWriteProxyView + ) -> %> + <% created_from_address = if @address && from_address_hash(@address), do: "0x" <> Base.encode16(from_address_hash(@address).bytes, case: :lower), else: nil %> + + <% ( + @view_module == Elixir.BlockScoutWeb.Tokens.TransferView || + @view_module == Elixir.BlockScoutWeb.Tokens.ReadContractView || + @view_module == Elixir.BlockScoutWeb.Tokens.HolderView || + @view_module == Elixir.BlockScoutWeb.Tokens.Instance.TransferView || + @view_module == Elixir.BlockScoutWeb.Tokens.Instance.MetadataView || + @view_module == Elixir.BlockScoutWeb.PageNotFoundView + ) -> %> + <% {:ok, created_from_address} = if @token && @token.contract_address_hash, do: Chain.hash_to_address(@token.contract_address_hash), else: {:ok, nil} %> + <% created_from_address_hash = if from_address_hash(created_from_address), do: "0x" <> Base.encode16(from_address_hash(created_from_address).bytes, case: :lower), else: nil %> + + <% true -> %> + <%= nil %> + <% end %> +
    + <% show_maintenance_alert = Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:show_maintenance_alert] %> + <%= if show_maintenance_alert do %> +
    + <%= raw(System.get_env("MAINTENANCE_ALERT_MESSAGE")) %> +
    + <% end %> + <% indexed_ratio_blocks = Explorer.Chain.indexed_ratio_blocks() %> + <% indexed_ratio = + case Chain.finished_blocks_indexing?(indexed_ratio_blocks) do + false -> indexed_ratio_blocks + _ -> Explorer.Chain.indexed_ratio_internal_transactions() + end %> + <%= if not Explorer.Chain.finished_indexing?(indexed_ratio_blocks) do %> +
    + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html" %> + + <%= gettext("- We're indexing this chain right now. Some of the counts may be inaccurate.") %> +
    + <% end %> + <%= render BlockScoutWeb.LayoutView, "_topnav.html", current_user: Conn.get_session(@conn, :current_user), conn: @conn %> + +
    + + + <%= @inner_content %> +
    + <%= render BlockScoutWeb.LayoutView, "_footer.html", assigns %> +
    + <%= if ( + @view_module != Elixir.BlockScoutWeb.ChainView && + @view_module != Elixir.BlockScoutWeb.BlockView && + @view_module != Elixir.BlockScoutWeb.BlockTransactionView && + @view_module != Elixir.BlockScoutWeb.AddressView && + @view_module != Elixir.BlockScoutWeb.TokensView && + @view_module != Elixir.BlockScoutWeb.TransactionView && + @view_module != Elixir.BlockScoutWeb.PendingTransactionView && + @view_module != Elixir.BlockScoutWeb.TransactionInternalTransactionView && + @view_module != Elixir.BlockScoutWeb.TransactionLogView && + @view_module != Elixir.BlockScoutWeb.TransactionRawTraceView && + @view_module != Elixir.BlockScoutWeb.TransactionTokenTransferView && + @view_module != Elixir.BlockScoutWeb.TransactionStateView && + @view_module != Elixir.BlockScoutWeb.AddressTransactionView && + @view_module != Elixir.BlockScoutWeb.AddressTokenTransferView && + @view_module != Elixir.BlockScoutWeb.AddressTokenView && + @view_module != Elixir.BlockScoutWeb.AddressInternalTransactionView && + @view_module != Elixir.BlockScoutWeb.AddressCoinBalanceView && + @view_module != Elixir.BlockScoutWeb.AddressLogsView && + @view_module != Elixir.BlockScoutWeb.AddressValidationView && + @view_module != Elixir.BlockScoutWeb.AddressContractView && + @view_module != Elixir.BlockScoutWeb.AddressContractVerificationView && + @view_module != Elixir.BlockScoutWeb.AddressContractVerificationViaJsonView && + @view_module != Elixir.BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView && + @view_module != Elixir.BlockScoutWeb.AddressContractVerificationVyperView && + @view_module != Elixir.BlockScoutWeb.AddressReadContractView && + @view_module != Elixir.BlockScoutWeb.AddressReadProxyView && + @view_module != Elixir.BlockScoutWeb.AddressWriteContractView && + @view_module != Elixir.BlockScoutWeb.AddressWriteProxyView && + @view_module != Elixir.BlockScoutWeb.Tokens.TransferView && + @view_module != Elixir.BlockScoutWeb.Tokens.ContractView && + @view_module != Elixir.BlockScoutWeb.Tokens.HolderView && + @view_module != Elixir.BlockScoutWeb.Tokens.InventoryView && + @view_module != Elixir.BlockScoutWeb.Tokens.InstanceView && + @view_module != Elixir.BlockScoutWeb.Tokens.Instance.MetadataView && + @view_module != Elixir.BlockScoutWeb.Tokens.Instance.OverviewView && + @view_module != Elixir.BlockScoutWeb.Tokens.Instance.TransferView && + @view_module != Elixir.BlockScoutWeb.VerifiedContractsView && + @view_module != Elixir.BlockScoutWeb.APIDocsView && + @view_module != Elixir.BlockScoutWeb.Admin.DashboardView && + @view_module != Elixir.BlockScoutWeb.SearchView && + @view_module != Elixir.BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView && + @view_module != Elixir.BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView && + @view_module != Elixir.BlockScoutWeb.StakesView + ) do %> + + <% end %> + <%= + for status <- ["error", "warning", "success", "question"] do + render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status + end + %> + <%= render_existing(@view_module, "scripts.html", assigns) %> + <%= if @view_module == Elixir.BlockScoutWeb.ChainView do %> + + + + <% 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/log/_data_decoded_view.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/log/_data_decoded_view.html.eex new file mode 100644 index 0000000..3c0fdaa --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/log/_data_decoded_view.html.eex @@ -0,0 +1,37 @@ +
    + " class="table thead-light table-bordered"> + + + + + + + <%= for {name, type, indexed?, value} <- @mapping do %> + + + + + + + <% end %> +
    <%= gettext "Name" %><%= gettext "Type" %><%= gettext "Indexed?" %><%= gettext "Data" %>
    <%= name %><%= type %><%= indexed? %> + <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %> + <% :error -> %> + <%= nil %> + <% copy_text -> %> + + + + + + <% end %> +
    <%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %>
    +
    +
    \ No newline at end of file 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 new file mode 100644 index 0000000..9d44b7f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex @@ -0,0 +1,12 @@ +
    +
    +
    + Page Not Found +
    +
    +

    <%= gettext "Page not found" %>

    +

    <%= gettext "The requested path was not found on BlockScout." %>

    + <%= gettext "Back Home" %> +
    +
    +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex new file mode 100644 index 0000000..57a9545 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex @@ -0,0 +1,35 @@ +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +

    <%= gettext "Pending Transactions" %>

    + +
    + <%= 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 %> +
    + + + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost, click to load newer transactions") %> + + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    + + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/_empty_td.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/_empty_td.html.eex new file mode 100644 index 0000000..ce2107a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_empty_td.html.eex @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/_name_td.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/_name_td.html.eex new file mode 100644 index 0000000..7a98114 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_name_td.html.eex @@ -0,0 +1,7 @@ + + <%= if @result.name do %> + + <%= highlight_search_result(@result.name, @query) %> + + <% end %> + \ 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 new file mode 100644 index 0000000..1df5a4d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex @@ -0,0 +1,94 @@ +-<%= if @result.block_hash, do: Base.encode16(@result.block_hash, case: :lower), else: "" %>"> + + <%= render BlockScoutWeb.SearchView, "_empty_td.html" %> + <%= case @result.type do %> + <% "token" -> %> + + + <%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %> + <% chain_id_for_token_icon = Application.get_env(:block_scout_web, :chain_id) %> + <% address_hash = @result.address_hash %> + <%= + render BlockScoutWeb.TokensView, + "_token_icon.html", + chain_id: chain_id_for_token_icon, + address: address_hash + %> + <% end %> + + + + <% res = @result.name <> " (" <> @result.symbol <> ")" %> + + <%= highlight_search_result(res, @query) %> + + + <% "address" -> %> + <%= render BlockScoutWeb.SearchView, "_empty_td.html" %> + <%= render BlockScoutWeb.SearchView, "_name_td.html", result: @result, query: @query, conn: @conn %> + <% "contract" -> %> + <%= render BlockScoutWeb.SearchView, "_empty_td.html" %> + <%= render BlockScoutWeb.SearchView, "_name_td.html", result: @result, query: @query, conn: @conn %> + <% "block" -> %> + <%= render BlockScoutWeb.SearchView, "_empty_td.html" %> + + <%= link( + highlight_search_result(to_string(@result.block_number), @query), + to: block_path(@conn, :show, @result.block_number) + ) %> + + <% _ -> %> + <%= render BlockScoutWeb.SearchView, "_empty_td.html" %> + <%= render BlockScoutWeb.SearchView, "_empty_td.html" %> + <% end %> + + <%= case @result.type do %> + <% "token" -> %> + <%= with {:ok, address_hash} = Chain.string_to_address_hash(@result.address_hash), + {:ok, address} <- Chain.hash_to_address(address_hash) do %> + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: address, + contract: false, + use_custom_tooltip: false + %> + <% end %> + <% "address" -> %> + <%= with {:ok, address_hash} = Chain.string_to_address_hash(@result.address_hash), + {:ok, address} <- Chain.hash_to_address(address_hash) do %> + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: address, + contract: false, + use_custom_tooltip: false + %> + <% end %> + <% "contract" -> %> + <%= with {:ok, address_hash} = Chain.string_to_address_hash(@result.address_hash), + {:ok, address} <- Chain.hash_to_address(address_hash) do %> + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: address, + contract: true, + use_custom_tooltip: false + %> + <% end %> + <% "transaction" -> %> + <%= render BlockScoutWeb.TransactionView, + "_link.html", + transaction_hash: "0x" <> Base.encode16(@result.tx_hash, case: :lower) %> + <% "block" -> %> + <%= link( + "0x" <> Base.encode16(@result.block_hash, case: :lower), + to: block_path(@conn, :show, @result.block_number) + ) %> + <% _ -> %> + <% end %> + + +
    <%= @result.type %>
    + + 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 new file mode 100644 index 0000000..c973f6c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/results.html.eex @@ -0,0 +1,51 @@ +
    " +> + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +
    + " placeholder="Search" id="search-text-input"> +
    + +
    + <%= 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 %> +
    +

    <%= gettext "Search Results" %>: <%= @query %>

    + +
    +
    + + + + + + + + + + + + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 5 %> + +
    +
     
    +
    +
     
    +
    +
    Search Result
    +
    +
    Hash
    +
    +
    Category
    +
    +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    +
    + +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_connect_container.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_connect_container.html.eex new file mode 100644 index 0000000..2438dd2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_connect_container.html.eex @@ -0,0 +1,16 @@ +
    + + <%= render BlockScoutWeb.IconsView, "_inactive_icon.html" %> + +

    Disconnected

    + +
    + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_function_response.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_function_response.html.eex new file mode 100644 index 0000000..e361e2c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_function_response.html.eex @@ -0,0 +1,44 @@ +
    +
    +[ <%= @function_name %> <%= gettext("method Response") %> ]
    +
    +
    +<%= case @outputs do %> + <% {:error, %{code: code, message: message, data: data}} -> %> + <% revert_reason = Chain.format_revert_reason_message(data) %> + <%= case decode_revert_reason(@smart_contract_address, revert_reason) do %> + <% {:ok, _identifier, text, mapping} -> %> +
    <%= raw(values_with_type(text, :error, nil)) %>
    +
    + <%= for {name, type, value} <- mapping do %> +
    <%= raw(values_with_type(value, type, name, 1)) %>
    + <% end %> +
    + <% {:error, _contract_verified, []} -> %> + <% decoded_revert_reason = decode_hex_revert_reason(revert_reason) %> +
    <%= "(#{code}) #{message} (#{if String.valid?(decoded_revert_reason), do: decoded_revert_reason, else: revert_reason})" %>
    + <% {:error, _contract_verified, candidates} -> %> + <% {:ok, _identifier, text, mapping} = Enum.at(candidates, 0) %> +
    <%= raw(values_with_type(text, :error, nil)) %>
    +
    + <%= for {name, type, value} <- mapping do %> +
    <%= raw(values_with_type(value, type, name, 1)) %>
    + <% end %> +
    + <% _ -> %> + <% decoded_revert_reason = decode_hex_revert_reason(revert_reason) %> +
    <%= "(#{code}) #{message} (#{if String.valid?(decoded_revert_reason), do: decoded_revert_reason, else: revert_reason})" %>
    + <% end %> + <% {:error, %{code: code, message: message}} -> %> +
    (error) : <%= "(#{code}) #{message}" %>
    + <% {:error, error} -> %> +
    (error) : <%= error %>
    + <% _ -> %> +
    +[<%= for {item, index} <- Enum.with_index(@outputs) do %>
    +<%= if named_argument?(item) do %><%= item["name"] %><% end %>
    +<%= raw(values_with_type(item["value"], item["type"], fetch_name(@names, index), 0)) %>
    +<% end %>]
    +<% end %>
    +
    +
    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 new file mode 100644 index 0000000..23ec24c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex @@ -0,0 +1,155 @@ +<% 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 %> + <%= render BlockScoutWeb.CommonComponentsView, "_minimal_proxy_pattern.html", address_hash: metadata_for_verification.address_hash, conn: @conn %> + <% else %> + <% path = address_verify_contract_path(@conn, :new, @address.hash) %> +
    + <%= 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, + to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)) %>.
    <%= gettext("All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with") %> <%= link( + gettext("Verify & Publish"), + to: path + ) %> <%= gettext("page") %>
    +
    + <% end %> + <% end %> +<% end %> +<%= if smart_contract_verified && @address.smart_contract.is_changed_bytecode do %> + <%= render BlockScoutWeb.CommonComponentsView, "_changed_bytecode_warning.html" %> +<% end %> +<%= if @contract_type == "proxy" do %> +
    +

    Implementation address:

    <%= link( + @implementation_address, + to: address_path(@conn, :show, @implementation_address) + ) %>

    +
    +<% end %> +<%= for {function, counter} <- Enum.with_index(@read_only_functions ++ @read_functions_required_wallet, 1) do %> +
    > +
    + <%= counter %>. + <%= case function["type"] do %> + <% "fallback" -> %> + <%= gettext "fallback" %><%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", text: gettext("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.") %> + <% "receive" -> %> + <%= gettext "receive" %><%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", text: gettext("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.") %> + <% _ -> %> + <%= function["name"] %> + <% end %> + → +
    + + <%= if queryable?(function["inputs"]) || writable?(function) || Helper.read_with_wallet_method?(function) do %> +
    + <% function_abi = + case Jason.encode([function]) do + {:ok, abi_string} -> + abi_string + _ -> + if @contract_type == "proxy" do + @implementation_abi + else + @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-custom-abi="<%= if assigns[:custom_abi], do: true, else: false %>"> + + + + <%= if queryable?(function["inputs"]) do %> + <%= for input <- function["inputs"] do %> +
    + <%= if int?(input["type"]) do %> + px;"/> + + + + + + + + <% else %> + " /> + <% end %> +
    + <% end %> + <% end %> + + <%= if Helper.payable?(function) do %> +
    + +
    + <% end %> + +
    + +
    +
    + + + <%= if outputs?(function["outputs"]) do %> +
    + <%= if (queryable?(function["inputs"])), do: raw "↳" %> + + <%= for output <- function["outputs"] do %> + <%= if output["name"] && output["name"] !== "", do: "#{output["name"]}(#{output["type"]})", else: output["type"] %> + <% end %> +
    + <% end %> +
    +
    + <% else %> + <%= cond do %> + <% outputs?(function["outputs"]) -> %> +
    + <% length = Enum.count(function["outputs"]) %> + <%= for {output, index} <- Enum.with_index(function["outputs"]) do %> + <%= if address?(output["type"]) do %> +
    + <%= link( + output["value"], + to: address_path(@conn, :show, output["value"])) %><%= if not_last_element?(length, index) do %>, <% end %> +
    + <% else %> + <%= if output["type"] == "uint256" do %> +
    +
    + (uint256): + "><%= output["value"] %> + + + <%= gettext("WEI")%> + <%= Explorer.coin_name() %> + +
    +
    + <% else %> +
    "><%= raw(values_with_type(output["value"], output["type"], [output["name"]], 0, output["components"])) %>
    + <% end %> + <% end %> + <% end %> +
    + <% error?(function["outputs"]) -> %> + <% {:error, text_error} = function["outputs"] %> +
    <%= text_error %>
    + <% true -> %> + <% nil %> + <% end %> + <% end %> +
    +<% end %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex new file mode 100644 index 0000000..316f571 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex @@ -0,0 +1,20 @@ + \ No newline at end of file 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 new file mode 100644 index 0000000..14a749e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex @@ -0,0 +1,49 @@ + + + + + <%= @index %> + + + + <%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %> + <% 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) %> + <%= + render BlockScoutWeb.TokensView, + "_token_icon.html", + chain_id: chain_id_for_token_icon, + address: token_hash_for_token_icon + %> + <% end %> + + + <% token = token_display_name(@token) %> + <%= link(token, + to: token_path(BlockScoutWeb.Endpoint, :show, @token.contract_address_hash), + "data-test": "token_link", + class: "text-truncate") %> + + + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: @token.contract_address, + contract: true, + use_custom_tooltip: false + %> + + + <%= if decimals?(@token) do %> + <%= format_according_to_decimals(@token.total_supply, @token.decimals) %> + <% else %> + <%= format_integer_to_currency(@token.total_supply) %> + <% end %> <%= @token.symbol %> + + + + + <%= @token.holder_count %> + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/_token_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_token_icon.html.eex new file mode 100644 index 0000000..b4db9e4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_token_icon.html.eex @@ -0,0 +1,2 @@ +<% token_icon_url = Explorer.Chain.get_token_icon_url_by(@chain_id, @address) %> +" alt="" onerror="this.style.visibility='hidden'"/> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/_metatags.html.eex new file mode 100644 index 0000000..e3754d4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/_metatags.html.eex @@ -0,0 +1 @@ +<%= BlockScoutWeb.Tokens.OverviewView.render "_metatags.html", conn: @conn, token: @token %> 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 new file mode 100644 index 0000000..99bb263 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/contract/index.html.eex @@ -0,0 +1,23 @@ +
    + <%= render( + OverviewView, + "_details.html", + token: @token, + counters_path: @counters_path, + tags: @tags, + conn: @conn + ) %> + +
    +
    + <%= render OverviewView, "_tabs.html", assigns %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %> +
    +
    +
    +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_metatags.html.eex new file mode 100644 index 0000000..e3754d4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_metatags.html.eex @@ -0,0 +1 @@ +<%= BlockScoutWeb.Tokens.OverviewView.render "_metatags.html", conn: @conn, token: @token %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex new file mode 100644 index 0000000..f8fecf5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex @@ -0,0 +1,19 @@ +
    +
    +
    + + <%= render BlockScoutWeb.AddressView, "_link.html", address: @token_balance.address, contract: BlockScoutWeb.AddressView.contract?(@token_balance.address), use_custom_tooltip: false %> + + + + + <%= format_token_balance_value(@token_balance.value, @token_balance.token_id, @token) %> <%= @token.symbol %> + + + <%= if show_total_supply_percentage?(@token.total_supply) do %> + <%= total_supply_percentage(@token_balance.value, @token.total_supply) %> + <% end %> + +
    +
    +
    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 new file mode 100644 index 0000000..9d78dcc --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex @@ -0,0 +1,44 @@ +
    + <%= render( + OverviewView, + "_details.html", + token: @token, + counters_path: @counters_path, + tags: @tags, + conn: @conn + ) %> + +
    +
    + <%= render OverviewView, "_tabs.html", assigns %> + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> +

    <%= gettext "Token Holders" %>

    + +
    + <%= 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 %> +
    + + +
    +
    + + <%= gettext "There are no holders for this Token." %> + +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    +
    + +
    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 new file mode 100644 index 0000000..49fc7ae --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex @@ -0,0 +1,60 @@ +
    " +> + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +

    <%= gettext "Tokens" %>

    + +
    + + <%= 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 %> +
    + +
    +
    + + + + + + + + + + + + + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 6 %> + +
    +
     
    +
    +
     
    +
    +
    Token
    +
    +
    Address
    +
    +
    + Total Supply +
    +
    +
    + Holders Count +
    +
    +
    +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    +
    + + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/holder/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/holder/index.html.eex new file mode 100644 index 0000000..7fe594a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/holder/index.html.eex @@ -0,0 +1,41 @@ +
    + <%= render( + OverviewView, + "_details.html", + token: @token, + total_token_transfers: @total_token_transfers, + token_id: @token_instance.token_id, + token_instance: @token_instance, + conn: @conn + ) %> + +
    +
    + <%= render OverviewView, "_tabs.html", assigns %> +
    +

    <%= gettext "Token Holders" %>

    + +
    + <%= 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 %> +
    + + + + + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex new file mode 100644 index 0000000..ca5cc7e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex @@ -0,0 +1,30 @@ +
    + <%= render( + OverviewView, + "_details.html", + token: @token, + total_token_transfers: @total_token_transfers, + token_id: @token_instance.token_id, + token_instance: @token_instance, + conn: @conn + ) %> + +
    +
    + <%= render OverviewView, "_tabs.html", assigns %> +
    +
    +
    +

    <%= gettext "Metadata" %>

    + +
    +
    +
    <%= format_metadata(@token_instance.instance.metadata) %>
    +            
    +
    +
    +
    +
    +
    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 new file mode 100644 index 0000000..bb2b4d7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex @@ -0,0 +1,97 @@ +
    +
    +
    +
    +
    +

    + <%= if token_name?(@token) do %> + <%= link(@token.name, to: token_path(BlockScoutWeb.Endpoint, :show, @token.contract_address_hash)) %> + <% else %> + <%= gettext("Token Details") %> + <% end %> + + + + ' + > + + + + + + + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["overview-title-item"], + clipboard_text: @token_id, + aria_label: gettext("Copy Token ID"), + title: gettext("Copy Token ID") %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_qr_code.html" %> + +

    + +

    <%= gettext "Token ID" %>: <%= to_string(@token_id) %>

    + +
    +
    + <%= if external_url(@token_instance.instance) do %> + + target="_blank"> + View In App <%= render BlockScoutWeb.IconsView, "_external_link.html" %> + + + <% end %> + <%= @token.type %> + <%= @total_token_transfers %> <%= gettext "Transfers" %> + <%= if decimals?(@token) do %> + <%= @token.decimals %> <%= gettext "Decimals" %> + <% end %> +
    +
    +
    +
    +
    + +
    +
    +
    +
    + <%= if media_type(media_src(@token_instance.instance, true)) == "video" do %> + + <% else %> + /> + <% end %> +
    +
    +
    +
    +
    + +
    + + +<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex new file mode 100644 index 0000000..de8a031 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex @@ -0,0 +1,22 @@ +
    + <%= link( + gettext("Token Transfers"), + class: "card-tab #{tab_status("token-transfers", @conn.request_path)}", + to: token_instance_path(@conn, :show, @token.contract_address_hash, to_string(@token_instance.token_id)) + ) + %> + <%= if @token_instance.instance && @token_instance.instance.metadata do %> + <%= link( + gettext("Metadata"), + to: token_instance_metadata_path(@conn, :index, Address.checksum(@token.contract_address_hash), to_string(@token_instance.token_id)), + class: "card-tab #{tab_status("metadata", @conn.request_path)}") + %> + <% end %> + <%= if !Chain.token_id_1155_is_unique?(@token.contract_address_hash, @token_instance.token_id) and @token.type == "ERC-1155" do %> + <%= link( + gettext("Token Holders"), + to: token_instance_holder_path(@conn, :index, Address.checksum(@token.contract_address_hash), to_string(@token_instance.token_id)), + class: "card-tab #{tab_status("token-holders", @conn.request_path)}") + %> + <% end %> +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex new file mode 100644 index 0000000..c49b042 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex @@ -0,0 +1,41 @@ +
    + <%= render( + OverviewView, + "_details.html", + token: @token, + total_token_transfers: @total_token_transfers, + token_id: @token_instance.token_id, + token_instance: @token_instance, + conn: @conn + ) %> + +
    +
    + <%= render OverviewView, "_tabs.html", assigns %> +
    +

    <%= gettext "Token Transfers" %>

    + +
    + <%= 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 %> +
    + + + + + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_metatags.html.eex new file mode 100644 index 0000000..e3754d4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_metatags.html.eex @@ -0,0 +1 @@ +<%= BlockScoutWeb.Tokens.OverviewView.render "_metatags.html", conn: @conn, token: @token %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex new file mode 100644 index 0000000..9d2ce63 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex @@ -0,0 +1,58 @@ +<% is_1155 = @token.type == "ERC-1155"%> +<% is_unique = Chain.token_id_1155_is_unique?(@token.contract_address_hash, @token_transfer.token_id) or not is_1155%> + +
    +
    +
    + + <%= if is_unique do%> + <%= gettext "Unique Token" %> + <% else %> + <%= gettext "Not unique Token" %> + <% end %> +
    + + <%= if is_unique do %> +
    + + <%= gettext "Token ID" %>: + + <%= link(@token_transfer.token_id, to: token_instance_path(@conn, :show, "#{@token.contract_address_hash}", "#{@token_transfer.token_id}")) %> + + + + <%= gettext "Owner Address" %>: + + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: @token_transfer.to_address, + contract: false, + use_custom_tooltip: false %> + + +
    + <% else %> +
    + + <%= gettext "Token ID" %>: + + <%= link(@token_transfer.token_id, to: token_instance_path(@conn, :show, "#{@token.contract_address_hash}", "#{@token_transfer.token_id}")) %> + + +
    + <% end %> + +
    + +
    + <%= if media_type(media_src(@token_transfer.instance)) == "video" do %> + + <% else %> + /> + <% end %> +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex new file mode 100644 index 0000000..e41c420 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex @@ -0,0 +1,42 @@ +
    + <%= render( + OverviewView, + "_details.html", + token: @token, + counters_path: @counters_path, + tags: @tags, + conn: @conn + ) %> + +
    +
    + <%= render OverviewView, "_tabs.html", assigns %> + +
    +

    <%= gettext "Inventory" %>

    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + + + +
    +
    + <%= gettext "There are no tokens." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    +
    + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex new file mode 100644 index 0000000..86470ae --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex @@ -0,0 +1,156 @@ +<% circles_addresses_list = CustomContractsHelpers.get_custom_addresses_list(:circles_addresses) %> +<% address_hash_str = "0x" <> Base.encode16(@token.contract_address_hash.bytes, case: :lower) %> +<% {:ok, created_from_address} = if @token.contract_address_hash, do: Chain.hash_to_address(@token.contract_address_hash), else: {:ok, nil} %> +<% created_from_address_hash = if from_address_hash(created_from_address), do: "0x" <> Base.encode16(from_address_hash(created_from_address).bytes, case: :lower), else: nil %> +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +
    +
    +

    +
    + <%= cond do %> + <% Enum.member?(circles_addresses_list, address_hash_str) -> %> +
    + +
    + <% Enum.member?(circles_addresses_list, created_from_address_hash) -> %> +
    + +
    + <% true -> %> + <%= nil %> + <% end %> + <%= if token_name?(@token) do %> + "> + +
    <%= @token.name %>
    + <% else %> + <%= gettext("Token Details") %> + <% end %> + <%= render BlockScoutWeb.AddressView, "_labels.html", address_hash: @token.contract_address_hash, tags: @tags %> +
    + + + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["overview-title-item"], + clipboard_text: Address.checksum(@token.contract_address_hash), + aria_label: gettext("Copy Address"), + title: gettext("Copy Address") %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_qr_code.html" %> + +

    +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Address of the token contract") %> + <%= gettext("Contract") %> +
    +
    + <%= link( + @token.contract_address_hash, + to: AccessHelpers.get_path(@conn, :address_path, :show, + Address.checksum(@token.contract_address_hash)), + "data-test": "token_contract_address" + ) + %> +
    +
    + <%= if total_supply?(@token) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The total amount of tokens issued") %> + <%= gettext("Total supply") %> +
    +
    + <%= if decimals?(@token) do %> + <%= format_according_to_decimals(@token.total_supply, @token.decimals) %> + <% else %> + <%= format_integer_to_currency(@token.total_supply) %> + <% end %> <%= @token.symbol %> +
    +
    + <%= if @token.usd_value do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Total Supply * Price") %> + <%= gettext("Market Cap") %> +
    +
    + +
    +
    + <%= unless Map.has_key?(@token, :custom_cap) && @token.custom_cap do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Price per token on the exchanges") %> + <%= gettext("Price") %> +
    +
    + +
    +
    + <% end %> + <% end %> + <% end %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Number of accounts holding the token") %> + <%= gettext("Holders") %> +
    +
    + <% link = if @conn.request_path |> String.contains?("/token-holders"), do: "", else: AccessHelpers.get_path(@conn, :token_holder_path, :index, @token.contract_address_hash) %> + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching holders...") %> +
    +
    +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Number of transfers for the token") %> + <%= gettext("Transfers") %> +
    +
    + <% link = if @conn.request_path |> String.contains?("/token-transfers"), do: "", else: AccessHelpers.get_path(@conn, :token_transfer_path, :index, @token.contract_address_hash) %> + <%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Fetching transfers...") %> +
    +
    + <%= if decimals?(@token) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Number of digits that come after the decimal place when displaying token value") %> + <%= gettext("Decimals") %> +
    +
    + <%= @token.decimals %> +
    +
    + <% end %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Type of the token standard") %> + <%= gettext("Token type") %> +
    +
    + <%= @token.type %> +
    +
    +
    +
    +
    +
    +
    + +<%= render BlockScoutWeb.CommonComponentsView, "_modal_qr_code.html", qr_code: BlockScoutWeb.AddressView.qr_code(Address.checksum(@token.contract_address_hash)), title: @token.contract_address %> +<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_metatags.html.eex new file mode 100644 index 0000000..7c4a80f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_metatags.html.eex @@ -0,0 +1,5 @@ + + <%= "#{token_name(@token)} (#{token_symbol(@token)}) - #{LayoutView.subnetwork_title()} - BlockScout" %> + + +"> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex new file mode 100644 index 0000000..38d779d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex @@ -0,0 +1,53 @@ +<% address_hash = Address.checksum(@token.contract_address_hash) %> +<% is_proxy = BlockScoutWeb.Tokens.OverviewView.smart_contract_is_proxy?(@token) %> +
    + <%= link( + gettext("Token Transfers"), + class: "card-tab #{tab_status("token-transfers", @conn.request_path)}", + to: AccessHelpers.get_path(@conn, :token_path, :show, @token.contract_address_hash) + ) + %> + <%= link( + gettext("Token Holders"), + class: "card-tab #{tab_status("token-holders", @conn.request_path)}", + "data-test": "token_holders_tab", + to: AccessHelpers.get_path(@conn, :token_holder_path, :index, address_hash) + ) + %> + <%= if display_inventory?(@token) do %> + <%= link( + gettext("Inventory"), + class: "card-tab #{tab_status("inventory", @conn.request_path)}", + to: AccessHelpers.get_path(@conn, :token_inventory_path, :index, address_hash) + ) + %> + <% end %> + <%= if smart_contract_with_read_only_functions?(@token) do %> + <%= link( + gettext("Read Contract"), + to: AccessHelpers.get_path(@conn, :token_read_contract_path, :index, address_hash), + class: "card-tab #{tab_status("read-contract", @conn.request_path)}") + %> + <% end %> + <%= if smart_contract_with_write_functions?(@token) do %> + <%= link( + gettext("Write Contract"), + to: AccessHelpers.get_path(@conn, :token_write_contract_path, :index, address_hash), + class: "card-tab #{tab_status("write-contract", @conn.request_path)}") + %> + <% end %> + <%= if is_proxy do %> + <%= link( + gettext("Read Proxy"), + to: AccessHelpers.get_path(@conn, :token_read_proxy_path, :index, address_hash), + class: "card-tab #{tab_status("read-proxy", @conn.request_path)}") + %> + <% end %> + <%= if smart_contract_with_write_functions?(@token) && is_proxy do %> + <%= link( + gettext("Write Proxy"), + to: AccessHelpers.get_path(@conn, :token_write_proxy_path, :index, address_hash), + class: "card-tab #{tab_status("write-proxy", @conn.request_path)}") + %> + <% end %> +
    \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_metatags.html.eex new file mode 100644 index 0000000..e3754d4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_metatags.html.eex @@ -0,0 +1 @@ +<%= BlockScoutWeb.Tokens.OverviewView.render "_metatags.html", conn: @conn, token: @token %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex new file mode 100644 index 0000000..d1d151f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex @@ -0,0 +1,50 @@ +
    +
    + +
    + + <%= render(BlockScoutWeb.CommonComponentsView, "_token_transfer_type_display_name.html", type: Chain.get_token_transfer_type(@token_transfer)) %> + +
    + +
    + <%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @token_transfer.transaction_hash %> + + <%= link to: address_token_transfers_path(@conn, :index, Address.checksum(@token_transfer.from_address), Address.checksum(@token.contract_address_hash)), "data-test": "address_hash_link" do %> + <%= render( + BlockScoutWeb.AddressView, + "_responsive_hash.html", + address: @token_transfer.from_address, + contract: BlockScoutWeb.AddressView.contract?(@token_transfer.from_address), + use_custom_tooltip: false + ) %> + <% end %> + → + <%= link to: address_token_transfers_path(@conn, :index, Address.checksum(@token_transfer.to_address), Address.checksum(@token.contract_address_hash)), "data-test": "address_hash_link" do %> + <%= render( + BlockScoutWeb.AddressView, + "_responsive_hash.html", + address: @token_transfer.to_address, + contract: BlockScoutWeb.AddressView.contract?(@token_transfer.to_address), + use_custom_tooltip: false + ) %> + <% end %> + + + + <%= render BlockScoutWeb.TransactionView, "_total_transfers.html", Map.put(assigns, :transfer, @token_transfer) %> + + +
    + +
    + + <%= link( + gettext("Block #%{number}", number: @token_transfer.block_number), + to: block_path(BlockScoutWeb.Endpoint, :show, @token_transfer.block_number) + ) %> + + +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex new file mode 100644 index 0000000..5ea6121 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex @@ -0,0 +1,42 @@ +
    + <%= render( + OverviewView, + "_details.html", + token: @token, + counters_path: @counters_path, + tags: @tags, + conn: @conn + ) %> + +
    +
    + <%= render OverviewView, "_tabs.html", assigns %> +
    +

    <%= gettext "Token Transfers" %>

    + +
    + <%= 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 %> +
    + + + + + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    + + +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex new file mode 100644 index 0000000..741344e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex @@ -0,0 +1,44 @@ +
    +
    +

    <%= gettext "Input" %>

    + + <%= case @decoded_input_data do %> + <% {:error, :contract_not_verified, candidates} -> %> +
    + <%= gettext "To see accurate decoded input data, the contract must be verified." %> + <%= case @transaction do %> + <% %{to_address: %{hash: hash}} -> %> + <% path = address_verify_contract_path(@conn, :new, hash) %> + <%= gettext "Verify the contract " %><%= gettext "here" %> + <% _ -> %> + <%= nil %> + <% end %> +
    + <%= unless Enum.empty?(candidates) do %> +

    <%= gettext "Potential matches from our contract method database:" %>

    + <%= gettext "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." %> + <%= gettext "To have guaranteed accuracy, use the link above to verify the contract's source code." %> + + <%= for {:ok, method_id, text, mapping} <- candidates do %> +
    +

    <%= text %>:

    + + <%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %> + <% end %> + <% end %> + <% {:ok, method_id, text, mapping} -> %> + <%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %> + <% {:error, :contract_verified, candidates} -> %> +

    <%= gettext "Potential matches from our contract method database:" %>

    + <%= for {:ok, method_id, text, mapping} <- candidates do %> +
    +

    <%= text %>:

    + <%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %> + <% end %> + <% _ -> %> +
    + <%= gettext "Failed to decode input data." %> +
    + <% end %> +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex new file mode 100644 index 0000000..3675d4c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex @@ -0,0 +1,62 @@ +
    + " class="table thead-light table-bordered"> + <%= if !assigns[:error] do %> + + + + + <% end %> + + + + +
    <%= gettext "Method Id" %>0x<%= @method_id %>
    <%= if assigns[:error], do: gettext("Error"), else: gettext("Call") %><%= @text %>
    +
    + +<% max_length = get_max_length() %> +<%= unless Enum.empty?(@mapping) do %> +
    + " class="table thead-light table-bordered"> + + + + + + <%= for {name, type, value} <- @mapping do %> + + + + + + <% end %> +
    <%= gettext "Name" %><%= gettext "Type" %><%= gettext "Data" %>
    <%= name %><%= type %> + <%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value, true) do %> + <% :error -> %> +
    + <%= gettext "Error rendering value" %> +
    + <% value_with_no_links -> %> + <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %> + <% :error -> %> + <%= nil %> + <% copy_text -> %> + + + + + + <% end %> + <% value_with_links = BlockScoutWeb.ABIEncodedValueView.value_html(type, value, false)%> + <% string = template_to_string(value_with_no_links) %> +
    <%= if String.length(string) > max_length do %>
    <% input = trim(max_length, string) %><%= input[:show] %>...<%= input[:hide] %>
    <% else %><%= value_with_links %><% end %>
    + <% end %> +
    +
    +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex new file mode 100644 index 0000000..4fb50ef --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex @@ -0,0 +1,34 @@ +
    +
    +
    + + <%= gettext("Emission Contract") %> + + + <%= gettext("Success") %> + +
    +
    + <%= link( + @validator.block.hash, + to: block_path(BlockScoutWeb.Endpoint, :show, @validator.block.hash), + class: "text-truncate") %> + + <%= @emission_funds |> BlockScoutWeb.AddressView.address_partial_selector(nil, @current_address) |> BlockScoutWeb.RenderHelpers.render_partial() %> + → + <%= @validator |> BlockScoutWeb.AddressView.address_partial_selector(nil, @current_address) |> BlockScoutWeb.RenderHelpers.render_partial() %> + + + + <%= format_wei_value(@emission_funds.reward, :ether) %> + + +
    +
    + + <%= @validator |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %> + + +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link.html.eex new file mode 100644 index 0000000..9b0333f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link.html.eex @@ -0,0 +1,4 @@ +<%= link(@transaction_hash, + to: transaction_path(BlockScoutWeb.Endpoint, :show, @transaction_hash), + "data-test": "transaction_hash_link", + class: "text-truncate") %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link_to_token_instance.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link_to_token_instance.html.eex new file mode 100644 index 0000000..88273f4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link_to_token_instance.html.eex @@ -0,0 +1 @@ +<%= "[" %><%= link(short_token_id(@token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@token_id)), "data-test": "token_link") %><%= "]" %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link_to_token_symbol.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link_to_token_symbol.html.eex new file mode 100644 index 0000000..73cb4a2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_link_to_token_symbol.html.eex @@ -0,0 +1 @@ +<%= link(token_symbol(@transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash), "data-test": "token_link") %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_metatags.html.eex new file mode 100644 index 0000000..920306f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_metatags.html.eex @@ -0,0 +1,14 @@ +<%= if assigns[:transaction] do %> + + <%= gettext( + "Transaction %{transaction} - %{subnetwork} Explorer", + transaction: to_string(@transaction.hash), + subnetwork: BlockScoutWeb.LayoutView.subnetwork_title() + ) %> + + + "> + "> +<% else %> + <%= BlockScoutWeb.LayoutView.render("_default_title.html") %> +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_pending_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_pending_tile.html.eex new file mode 100644 index 0000000..d1b33ff --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_pending_tile.html.eex @@ -0,0 +1,25 @@ +<% status = BlockScoutWeb.TransactionView.transaction_status(@transaction) %> +
    +
    +
    + <%= BlockScoutWeb.TransactionView.transaction_display_type(@transaction) %> +
    <%= BlockScoutWeb.TransactionView.formatted_result(status) %>
    +
    +
    + <%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @transaction.hash %> + + <%= render BlockScoutWeb.AddressView, "_link.html", address: @transaction.from_address, contract: BlockScoutWeb.AddressView.contract?(@transaction.from_address), use_custom_tooltip: false %> + → + <%= if @transaction.to_address_hash do %> + <%= render BlockScoutWeb.AddressView, "_link.html", address: @transaction.to_address, contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address), use_custom_tooltip: false %> + <% else %> + <%= gettext("Contract Address Pending") %> + <% end %> + + + <%= BlockScoutWeb.TransactionView.value(@transaction, include_label: false) %> <%= Explorer.coin_name() %> + <%= BlockScoutWeb.TransactionView.formatted_fee(@transaction, denomination: :ether, include_label: false) %> <%= gettext "TX Fee" %> + +
    +
    +
    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 new file mode 100644 index 0000000..c99a8f1 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tabs.html.eex @@ -0,0 +1,34 @@ +
    + <%= if @show_token_transfers do %> + <%= link( + gettext("Token Transfers"), + class: "card-tab #{tab_status("token-transfers", @conn.request_path, @show_token_transfers)}", + to: AccessHelpers.get_path(@conn, :transaction_token_transfer_path, :index, @transaction) + ) + %> + <% end %> + <%= link( + gettext("Internal Transactions"), + class: "card-tab #{tab_status("internal-transactions", @conn.request_path, @show_token_transfers)}", + to: AccessHelpers.get_path(@conn, :transaction_internal_transaction_path, :index, @transaction) + ) + %> + <%= link( + gettext("Logs"), + class: "card-tab #{tab_status("logs", @conn.request_path)}", + to: AccessHelpers.get_path(@conn, :transaction_log_path, :index, @transaction), + "data-test": "transaction_logs_link" + ) + %> + <%= link( + gettext("Raw Trace"), + 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 new file mode 100644 index 0000000..f555c37 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex @@ -0,0 +1,99 @@ +<% 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) %> +
    +
    + +
    +
    + <%= if error_in_internal_tx do %> + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", text: gettext("Error in internal transactions"), additional_classes: ["color-inherit"] %> + <% end %> + + <%= transaction_display_type(@transaction) %> + +
    + + <%= if status_class(@transaction) == "tile-status--pending" do %> +
    + + +
    + <% end %> + <%= formatted_result(status) %> +
    +
    + +
    + +
    + <%= render "_link.html", transaction_hash: @transaction.hash, data_test: "address_hash_link" %> + <% method_name = Transaction.get_method_name(@transaction) %> + <%= 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 %> +
    +
    + + <%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:from, assigns[:current_address]) |> BlockScoutWeb.RenderHelpers.render_partial() %> + → + <%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:to, assigns[:current_address]) |> BlockScoutWeb.RenderHelpers.render_partial() %> + + + + <%= value(@transaction, include_label: false) %> <%= Explorer.coin_name() %> + + + <%= formatted_fee(@transaction, denomination: :ether, include_label: false) %> <%= gettext "TX Fee" %> + + + + + <%= if involves_token_transfers?(@transaction) do %> +
    + <% [first_token_transfer | remaining_token_transfers] = @transaction.token_transfers %> + + <%= render "_token_transfer.html", address: assigns[:current_address], token_transfer: first_token_transfer %> + +
    + <%= for token_transfer <- remaining_token_transfers do %> + <%= render "_token_transfer.html", address: assigns[:current_address], token_transfer: token_transfer, burn_address_hash: @burn_address_hash %> + <% end %> +
    +
    + + <%= if Enum.any?(remaining_token_transfers) do %> +
    + <%= link gettext("View More Transfers"), to: "#transaction-#{@transaction.hash}", "data-toggle": "collapse", "data-selector": "token-transfer-open", "data-test": "token_transfers_expansion" %> + <%= link gettext("View Less Transfers"), class: "d-none", to: "#transaction-#{@transaction.hash}", "data-toggle": "collapse", "data-selector": "token-transfer-close" %> +
    + <% end %> + <% end %> +
    + +
    + + <%= @transaction |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %> + + + <%= if from_or_to_address?(@transaction, assigns[:current_address]) do %> + + <%= if @transaction.from_address_hash == assigns[:current_address].hash do %> + + <%= gettext "OUT" %> + + <% else %> + + <%= gettext "IN" %> + + <% end %> + + <% end %> +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex new file mode 100644 index 0000000..815d9bd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex @@ -0,0 +1,25 @@ +
    + + <%= if from_or_to_address?(@token_transfer, @address) do %> + <%= if @token_transfer.from_address_hash == @address.hash do %> + + ↳ + + <% else %> + + ↳ + + <% end %> + <% end %> + + <%= @token_transfer |> BlockScoutWeb.AddressView.address_partial_selector(:from, @address, true) |> BlockScoutWeb.RenderHelpers.render_partial() %> + + → + + <%= @token_transfer |> BlockScoutWeb.AddressView.address_partial_selector(:to, @address, true) |> BlockScoutWeb.RenderHelpers.render_partial() %> + + + + <%= render BlockScoutWeb.TransactionView, "_total_transfers.html", Map.put(assigns, :transfer, @token_transfer) %> + +
    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 new file mode 100644 index 0000000..fd9efa4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex @@ -0,0 +1,23 @@ +<%= case token_transfer_amount(@transfer) do %> + <% {:ok, :erc721_instance} -> %> + <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> + <% {: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 %> + <% else %> + <%= "#{value} " %> + <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> + <% end %> + <% {:ok, :erc1155_instance, values, token_ids, _decimals} -> %> + <% values_ids = Enum.zip(values, token_ids) %> + <%= for {value, token_id} <- values_ids do %> +
    + <%= "#{value} "%> + <%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: token_id %> +
    + <% end %> + <% {:ok, value} -> %> + <%= value %> + <%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %> +<% end %> \ No newline at end of file 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 new file mode 100644 index 0000000..d26557b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex @@ -0,0 +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 %> +<% 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.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.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 %> + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_transfer_token_with_id.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_transfer_token_with_id.html.eex new file mode 100644 index 0000000..44a3444 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_transfer_token_with_id.html.eex @@ -0,0 +1,2 @@ +<%= "TokenID " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_instance.html", transfer: @transfer, token_id: @token_id %> +<%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex new file mode 100644 index 0000000..37f358b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex @@ -0,0 +1,45 @@ + +
    + <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +

    <%= gettext "Validated Transactions" %>

    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_rap_pagination_container.html", position: "top", showing_limit: if Chain.transactions_available_count() == Chain.limit_showing_transactions(), do: Chain.limit_showing_transactions(), else: nil %> +
    + + + <%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost, click to load newer transactions") %> + + + +
    +
    + + <%= gettext "There are no transactions." %> + +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_rap_pagination_container.html", position: "bottom" %> + +
    + + +
    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 new file mode 100644 index 0000000..8ec4188 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/invalid.html.eex @@ -0,0 +1,14 @@ +
    +
    +
    +
    +
    +

    <%= 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 new file mode 100644 index 0000000..97f721e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/not_found.html.eex @@ -0,0 +1,34 @@ +
    +
    +
    + Block 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") %> +
    +
    + +
    \ 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 new file mode 100644 index 0000000..cefcc6c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -0,0 +1,512 @@ +<% 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 %> +<% base_fee_per_gas = if block, do: block.base_fee_per_gas, else: nil %> +<% max_priority_fee_per_gas = @transaction.max_priority_fee_per_gas %> +<% max_fee_per_gas = @transaction.max_fee_per_gas %> +<% burned_fee = + 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 %> +<% %Wei{value: burned_fee_decimal} = if is_nil(burned_fee), do: %Wei{value: Decimal.new(0)}, else: burned_fee %> +<% priority_fee_per_gas = 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) %> +<% priority_fee = if is_nil(priority_fee_per_gas), do: nil, else: Wei.mult(priority_fee_per_gas, @transaction.gas_used) %> +<% decoded_input_data = decoded_input_data(@transaction) %> +<% status = transaction_status(@transaction) %> +<% circles_addresses_list = CustomContractsHelpers.get_custom_addresses_list(:circles_addresses) %> +<% address_hash_str = if to_address_hash, do: "0x" <> Base.encode16(to_address_hash.bytes, case: :lower), else: nil %> +<% {:ok, created_from_address} = if to_address_hash, do: Chain.hash_to_address(to_address_hash), else: {:ok, nil} %> +<% created_from_address_hash_str = if from_address_hash(created_from_address), do: "0x" <> Base.encode16(from_address_hash(created_from_address).bytes, case: :lower), else: nil %> +<%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> +
    +
    +
    + +
    +
    + <%= cond do %> + <% Enum.member?(circles_addresses_list, address_hash_str) -> %> +
    + +
    + <% Enum.member?(circles_addresses_list, created_from_address_hash_str) -> %> +
    + +
    + <% true -> %> + <%= nil %> + <% end %> +

    +
    + <%= 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 %> +
    +
    + + +
    + <%= gettext("This transaction is pending confirmation.") %> +
    + <% end %> +
    + <%= if show_tenderly_link?() do %> +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tenderly_link.html", + transaction_hash: @transaction.hash, + tenderly_chain_path: tenderly_chain_path() %> +
    + <% end %> +
    +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Unique character string (TxID) assigned to every verified transaction.") %> + <%= gettext "Transaction Hash" %> +
    +
    + <%= @transaction %> + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], + clipboard_text: @transaction, + aria_label: gettext("Copy Transaction Hash"), + title: gettext("Copy Txn Hash") %> +
    +
    + + + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Current transaction state: Success, Failed (Error), or Pending (In Process)") %> + <%= gettext "Result" %> +
    +
    + <% formatted_result = BlockScoutWeb.TransactionView.formatted_result(status) %> + <%= render BlockScoutWeb.CommonComponentsView, "_status_icon.html", status: status %><%= formatted_result %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The status of the transaction: Confirmed or Unconfirmed.") %> + <%= gettext "Status" %> +
    +
    + <% formatted_status = BlockScoutWeb.TransactionView.formatted_status(status) %> + <% confirmations = confirmations(@transaction, block_height: @block_height) %> + + + <%= if status == :pending do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: formatted_status, additional_classes: ["large"] %> + <% else %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: formatted_status, additional_classes: ["success", "large"] %> + <% end %> + + <%= if confirmations > 0 do %> + <%= gettext "Confirmed by " %><%= confirmations %><%= " " <> confirmations_ds_name(confirmations) %> + <% end %> + +
    +
    + + <%= if status == {:error, "Reverted"} || status == {:error, "execution reverted"} do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The revert reason of the transaction.") %> + <%= gettext "Revert reason" %>
    +
    + <%= case BlockScoutWeb.TransactionView.transaction_revert_reason(@transaction) do %> + <% {:error, _contract_not_verified, candidates} when candidates != [] -> %> + <% {:ok, method_id, text, mapping} = Enum.at(candidates, 0) %> + <%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping, error: true) %> + <% {:ok, method_id, text, mapping} -> %> + <%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping, error: true) %> + <% _ -> %> + <% hex = BlockScoutWeb.TransactionView.get_pure_transaction_revert_reason(@transaction) %> + <% utf8 = BlockScoutWeb.TransactionView.decoded_revert_reason(@transaction) %> +
    +
    Raw:<%= raw("\t") %><%= hex %><%= raw("\n") %>UTF-8:<%= raw("\t") %><%= utf8 %>
    +
    + <% end %> +
    +
    + <% end %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Block number containing the transaction.") %> + <%= gettext "Block" %>
    +
    + <%= if block do %> + <%= link( + block, + class: "transaction__link", + to: block_path(@conn, :show, block) + ) %> + <% else %> + <%= formatted_result(status) %> + <% end %> +
    +
    + + <%= if block && block.timestamp do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Date & time of transaction inclusion, including length of time for confirmation.") %> + <%= gettext "Timestamp" %> +
    +
    + + + + + <%= case processing_time_duration(@transaction) do %> + <% :pending -> %> + <% nil %> + <% :unknown -> %> + <% nil %> + <% {:ok, interval_string} -> %> + | <%= gettext("Confirmed within") %> <%= interval_string %> + <% end %> +
    +
    + <% end %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Address (external or contract) sending the transaction.") %> + <%= gettext "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.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], + clipboard_text: Address.checksum(from_address_hash), + aria_label: gettext("Copy From Address"), + title: gettext("Copy From Address") %> +
    +
    + + <% to_address = @transaction |> Map.get(:to_address) || @transaction |> Map.get(:created_contract_address) %> + <% recipient_address_hash = to_address_hash || created_address_hash %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Address (external or contract) receiving the transaction.") %> + <%= if BlockScoutWeb.AddressView.contract?(to_address) && !created_address_hash do %> + <%= gettext "Interacted With (To)" %> + <% else %> + <%= gettext "To" %> + <% end %> +
    +
    + <%= cond do %> + <% created_address_hash -> %> + [<%= gettext("Contract") %>  + <%= 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: Address.checksum(recipient_address_hash), + aria_label: gettext("Copy To Address"), + title: gettext("Copy To Address") %> + <% 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: Address.checksum(recipient_address_hash), + aria_label: gettext("Copy To Address"), + title: gettext("Copy To Address") %> + <% true -> %> + <% end %> +
    +
    + <%= case token_transfer_type(@transaction) do %> + <% {_type, %{token_transfers: token_transfers} = transaction_with_transfers} when is_list(token_transfers) and token_transfers != [] -> %> + + <% %{transfers: transfers, mintings: mintings, burnings: burnings, creations: creations} = aggregate_token_transfers(transaction_with_transfers.token_transfers) %> + <%= if Enum.count(transfers) > 0 do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("List of token transferred in the transaction.") %> + <%= gettext "Tokens Transferred" %>
    +
    + + <%= for transfer <- transfers do %> + <%= render BlockScoutWeb.TransactionView, "_total_transfers_from_to.html", Map.put(assigns, :transfer, transfer) %> + <% end %> +
    +
    +
    + <% end %> + + <%= if Enum.count(mintings) > 0 do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("List of token minted in the transaction.") %> + <%= gettext "Tokens Minted" %> +
    +
    + + <%= for minting <- mintings do %> + <%= render BlockScoutWeb.TransactionView, "_total_transfers_from_to.html", Map.put(assigns, :transfer, minting) %> + <% end %> +
    +
    +
    + <% end %> + + <%= if Enum.count(burnings) > 0 do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("List of token burnt in the transaction.") %> + <%= gettext "Tokens Burnt" %>
    +
    + <%= for burning <- burnings do %> + + <%= render BlockScoutWeb.TransactionView, "_total_transfers_from_to.html", Map.put(assigns, :transfer, burning) %> +
    + <% end %> +
    +
    + <% end %> + + <%= if Enum.count(creations) > 0 do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("List of ERC-1155 tokens created in the transaction.") %> + <%= gettext "Tokens Created" %>
    +
    + <%= for creation <- creations do %> + + <%= render BlockScoutWeb.TransactionView, "_total_transfers_from_to.html", Map.put(assigns, :transfer, creation) %> +
    + <% end %> +
    +
    + <% end %> + <% _ -> %> + <% end %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Value sent in the native token (and USD) if applicable.") %> + <%= gettext "Value" %> +
    +
    <%= value(@transaction) %> + <%= if !empty_exchange_rate?(@exchange_rate) do %> + ( + data-usd-exchange-rate=<%= @exchange_rate.usd_value %>> + ) + <% end %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Total transaction fee.") %> + <%= gettext "Transaction Fee" %> +
    +
    + <%= formatted_fee(@transaction, denomination: :ether) %> + + <%= if !empty_exchange_rate?(@exchange_rate) do %> + ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>) + <% end %> +
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage.") %> + <%= gettext "Gas Price" %> +
    +
    <%= gas_price(@transaction, :gwei) %>
    +
    + <%= if !is_nil(type) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Transaction type, introduced in EIP-2718.") %> + <%= gettext "Transaction Type" %> +
    +
    <%= type %>
    +
    + <% end %> +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Maximum gas amount approved for the transaction.") %> + <%= gettext "Gas Limit" %> +
    +
    <%= format_gas_limit(@transaction.gas) %>
    +
    + <%= if !is_nil(max_fee_per_gas) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee.") %> + <%= gettext "Max Fee per Gas" %> +
    +
    <%= format_wei_value(max_fee_per_gas, :gwei) %>
    +
    + <% end %> + <%= if !is_nil(max_priority_fee_per_gas) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization.") %> + <%= gettext "Max Priority Fee per Gas" %> +
    +
    <%= format_wei_value(max_priority_fee_per_gas, :gwei) %>
    +
    + <% end %> + <%= if !is_nil(priority_fee) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("User-defined tip sent to validator for transaction priority/inclusion.") %> + <%= gettext "Priority Fee / Tip" %> +
    +
    <%= format_wei_value(priority_fee, :ether) %>
    +
    + <% end %> + <%= if !is_nil(burned_fee) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + 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) %> + <%= unless empty_exchange_rate?(@exchange_rate) do %> + ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>) + <% end %> +
    +
    + <% end %> + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Actual gas amount used by the transaction.") %> + <%= gettext "Gas Used by Transaction" %> +
    + <% gas_used_perc = gas_used_perc(@transaction) %> +
    <%= gas_used(@transaction) %> <%= if gas_used_perc, do: "| #{gas_used_perc}%" %>
    +
    + +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1.") %> + <%= gettext "Nonce" %>"><%= gettext "Position" %> +
    +
    <%= @transaction.nonce %><%= if block, do: @transaction.index, else: formatted_result(status) %>
    +
    + <%= unless value_transfer?(@transaction) do %> +
    +
    + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Binary data included with the transaction. See input / logs below for additional info.") %> + <%= gettext "Raw Input" %> +
    +
    +
    + + +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + id: "tx-raw-input", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-no-borders", "btn-copy-icon-ml-0", "btn-copy-tx-raw-input", "tx-raw-input"], + clipboard_text: @transaction.input, + aria_label: gettext("Copy Value"), + title: gettext("Copy Txn Hex Input") %> + + <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-no-borders", "btn-copy-icon-ml-0", "btn-copy-tx-raw-input", "tx-utf8-input"], + clipboard_text: @transaction.input.bytes, + aria_label: gettext("Copy Value"), + title: gettext("Copy Txn UTF-8 Input"), + style: "display: none;" %> +
    +
    + +
    +
    +
    <%= @transaction.input %>
    +
    +
    + + +
    +
    + <% end %> +
    +
    +
    +
    + + <%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> + + <%= unless skip_decoding?(@transaction) do %> +
    +
    + <%= render BlockScoutWeb.TransactionView, "_decoded_input.html", Map.put(assigns, :decoded_input_data, decoded_input_data) %> +
    +
    + <% end %> + +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/show_internal_transactions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/show_internal_transactions.html.eex new file mode 100644 index 0000000..cbfb925 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/show_internal_transactions.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.TransactionInternalTransactionView, "index.html", assigns %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/show_token_transfers.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/show_token_transfers.html.eex new file mode 100644 index 0000000..b524c8e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/show_token_transfers.html.eex @@ -0,0 +1 @@ +<%= render BlockScoutWeb.TransactionTokenTransferView, "index.html", assigns %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/_metatags.html.eex new file mode 100644 index 0000000..85c3d66 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/_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_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex new file mode 100644 index 0000000..0194017 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex @@ -0,0 +1,29 @@ +
    + <%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> +
    + <%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %> +
    +

    <%= gettext "Internal Transactions" %>

    + +
    + <%= 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 %> +
    + + +
    +
    + <%= gettext "There are no internal transactions for this transaction." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    +
    + +
    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 new file mode 100644 index 0000000..e64f3bd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex @@ -0,0 +1,139 @@ +
    + <% decoded_result = decode(@log, @transaction) %> + <%= case decoded_result do %> + <% {:error, :contract_not_verified, _cadidates} -> %> +
    + <%= gettext "To see accurate decoded input data, the contract must be verified." %> + <%= case @log do %> + <% %{address_hash: %Explorer.Chain.Hash{} = address_hash} -> %> + <% path = address_verify_contract_path(@conn, :new, address_hash) %> + <%= gettext "Verify the contract " %><%= gettext "here" %> + <% _ -> %> + <%= nil %> + <% end %> +
    + <% _ -> %> + <%= nil %> + <% end %> + +
    +
    <%= gettext "Address" %>
    +
    +

    + <% name = implementation_name(@log.address) || primary_name(@log.address)%> + <%= link( + (if name, do: name <> " | "<> to_string(@log.address), else: @log.address), + to: address_path(@conn, :show, @log.address), + "data-test": "log_address_link", + "data-address-hash": @log.address + ) %> +

    +
    + <%= case decoded_result do %> + <% {:error, :could_not_decode} -> %> +
    <%= gettext "Decoded" %>
    +
    +
    + <%= gettext "Failed to decode log data." %> +
    + <% {:error, :no_matching_function} -> %> + <%= nil %> + <% {:ok, method_id, text, mapping} -> %> +
    <%= gettext "Decoded" %>
    +
    + + + + + + + + + +
    Method Id0x<%= method_id %>
    Call<%= text %>
    + <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> + <% {:error, :contract_not_verified, results} -> %> + <%= for {:ok, method_id, text, mapping} <- results do %> +
    <%= gettext "Decoded" %>
    +
    + + + + + + + + + +
    Method Id0x<%= method_id %>
    Call<%= text %>
    + <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> + <% end %> + <% {:error, :contract_verified, results} -> %> + <%= for {:ok, method_id, text, mapping} <- results do %> +
    <%= gettext "Decoded" %>
    +
    + + + + + + + + + +
    Method Id0x<%= method_id %>
    Call<%= text %>
    + <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> + <% end %> + <% _ -> %> + <%= nil %> + <% end %> + +
    <%= gettext "Topics" %>
    +
    +
    + <%= unless is_nil(@log.first_topic) do %> +
    + [0] + <%= @log.first_topic %> +
    + <% end %> + <%= unless is_nil(@log.second_topic) do %> +
    + [1] + <%= @log.second_topic %> +
    + <% end %> + <%= unless is_nil(@log.third_topic) do %> +
    + [2] + <%= @log.third_topic %> +
    + <% end %> + <%= unless is_nil(@log.fourth_topic) do %> +
    + [3] + <%= @log.fourth_topic %> +
    + <% end %> +
    +
    +
    + <%= gettext "Data" %> +
    +
    + <%= unless is_nil(@log.data) do %> +
    + <%= @log.data %> +
    + <% end %> +
    +
    + <%= gettext "Log Index" %> +
    +
    +
    + <%= @log.index %> +
    +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_metatags.html.eex new file mode 100644 index 0000000..85c3d66 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_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_log/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex new file mode 100644 index 0000000..88db746 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex @@ -0,0 +1,32 @@ +
    + <%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> + +
    + <%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %> + +
    +

    <%= gettext "Logs" %>

    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + + + +
    +
    + <%= gettext "There are no logs for this transaction." %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/_metatags.html.eex new file mode 100644 index 0000000..85c3d66 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/_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_raw_trace/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex new file mode 100644 index 0000000..8bb5c03 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex @@ -0,0 +1,28 @@ +
    + <%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> + +
    + <%= 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 %> +
    + <%= 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 0000000..85c3d66 --- /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 0000000..180d51d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex @@ -0,0 +1,67 @@ + +<% 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 0000000..6803a22 --- /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 0000000..c62dbb1 --- /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/transaction_token_transfer/_metatags.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_metatags.html.eex new file mode 100644 index 0000000..85c3d66 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_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_token_transfer/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex new file mode 100644 index 0000000..885990a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex @@ -0,0 +1,20 @@ +
    +
    +
    + <%= render(BlockScoutWeb.CommonComponentsView, "_token_transfer_type_display_name.html", type: Chain.get_token_transfer_type(@token_transfer)) %> +
    + +
    + <%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @token_transfer.transaction_hash %> + + <%= render BlockScoutWeb.AddressView, "_link.html", address: @token_transfer.from_address, contract: BlockScoutWeb.AddressView.contract?(@token_transfer.from_address), use_custom_tooltip: false %> + → + <%= render BlockScoutWeb.AddressView, "_link.html", address: @token_transfer.to_address, contract: BlockScoutWeb.AddressView.contract?(@token_transfer.to_address), use_custom_tooltip: false %> + + + + <%= render BlockScoutWeb.TransactionView, "_total_transfers.html", Map.put(assigns, :transfer, @token_transfer) %> + +
    +
    +
    diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex new file mode 100644 index 0000000..46f8214 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex @@ -0,0 +1,32 @@ +
    + <%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> + +
    + <%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %> +
    +

    <%= gettext "Token Transfers" %>

    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
    + + + +
    +
    + <%= gettext "There are no token transfers for this transaction" %> +
    +
    + +
    + <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
    + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
    +
    + +
    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 0000000..dbd6471 --- /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 0000000..88e38e1 --- /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 0000000..d69ebe0 --- /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 0000000..b8f801b --- /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/tracer.ex b/apps/block_scout_web/lib/block_scout_web/tracer.ex new file mode 100644 index 0000000..6bca34d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/tracer.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.Tracer do + @moduledoc false + + use Spandex.Tracer, otp_app: :block_scout_web +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 new file mode 100644 index 0000000..77f4fa3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/abi_encoded_value_view.ex @@ -0,0 +1,213 @@ +defmodule BlockScoutWeb.ABIEncodedValueView do + @moduledoc """ + Renders a decoded value that is encoded according to an ABI. + + Does not leverage an eex template because it renders formatted + values via `
    ` tags, and that is hard to do in an eex template.
    +  """
    +  use BlockScoutWeb, :view
    +
    +  alias ABI.FunctionSelector
    +  alias Phoenix.HTML
    +
    +  require Logger
    +
    +  def value_html(type, value, no_links \\ false)
    +
    +  def value_html(type, value, no_links) do
    +    decoded_type = FunctionSelector.decode_type(type)
    +
    +    do_value_html(decoded_type, value, no_links)
    +  rescue
    +    exception ->
    +      Logger.warn(fn ->
    +        ["Error determining value html for #{inspect(type)}: ", Exception.format(:error, exception)]
    +      end)
    +
    +      :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)
    +
    +    do_copy_text(decoded_type, value)
    +  rescue
    +    exception ->
    +      Logger.warn(fn ->
    +        ["Error determining copy text for #{inspect(type)}: ", Exception.format(:error, exception)]
    +      end)
    +
    +      :error
    +  end
    +
    +  defp do_copy_text({:bytes, _type}, value) do
    +    hex(value)
    +  end
    +
    +  defp do_copy_text({:array, type, _}, value) do
    +    do_copy_text({:array, type}, value)
    +  end
    +
    +  defp do_copy_text({:array, type}, value) do
    +    values =
    +      value
    +      |> Enum.map(&do_copy_text(type, &1))
    +      |> Enum.intersperse(", ")
    +
    +    ~E|[<%= values %>]|
    +  end
    +
    +  defp do_copy_text(_, {:dynamic, value}) do
    +    hex(value)
    +  end
    +
    +  defp do_copy_text(type, value) when type in [:bytes, :address] do
    +    hex(value)
    +  end
    +
    +  defp do_copy_text({:tuple, types}, value) do
    +    values =
    +      value
    +      |> Tuple.to_list()
    +      |> Enum.with_index()
    +      |> Enum.map(fn {val, ind} -> do_copy_text(Enum.at(types, ind), val) end)
    +      |> Enum.intersperse(", ")
    +
    +    ~E|(<%= values %>)|
    +  end
    +
    +  defp do_copy_text(_type, value) do
    +    to_string(value)
    +  end
    +
    +  defp do_value_html(type, value, no_links, depth \\ 0)
    +
    +  defp do_value_html({:bytes, _}, value, no_links, depth) do
    +    do_value_html(:bytes, value, no_links, depth)
    +  end
    +
    +  defp do_value_html({:array, type, _}, value, no_links, depth) do
    +    do_value_html({:array, type}, value, no_links, depth)
    +  end
    +
    +  defp do_value_html({:array, type}, value, no_links, depth) do
    +    values =
    +      Enum.map(value, fn inner_value ->
    +        do_value_html(type, inner_value, no_links, depth + 1)
    +      end)
    +
    +    spacing = String.duplicate(" ", depth * 2)
    +    delimited = Enum.intersperse(values, ",\n")
    +
    +    ~E|<%= spacing %>[<%= "\n" %><%= delimited %><%= "\n" %><%= spacing %>]|
    +  end
    +
    +  defp do_value_html({:tuple, types}, values, no_links, _) do
    +    values_list =
    +      values
    +      |> Tuple.to_list()
    +      |> Enum.with_index()
    +      |> Enum.map(fn {value, i} ->
    +        do_value_html(Enum.at(types, i), value, no_links)
    +      end)
    +
    +    delimited = Enum.intersperse(values_list, ",")
    +    ~E|(<%= delimited %>)|
    +  end
    +
    +  defp do_value_html(type, value, no_links, depth) do
    +    spacing = String.duplicate(" ", depth * 2)
    +    ~E|<%= spacing %><%=base_value_html(type, value, no_links)%>|
    +    [spacing, base_value_html(type, value, no_links)]
    +  end
    +
    +  defp base_value_html(_, {:dynamic, value}, _no_links) do
    +    ~E|<%= hex(value) %>|
    +  end
    +
    +  defp base_value_html(:address, value, no_links) do
    +    if no_links do
    +      base_value_html(:address_text, value, no_links)
    +    else
    +      address = hex(value)
    +
    +      ~E|<%= address %>|
    +    end
    +  end
    +
    +  defp base_value_html(:address_text, value, _no_links) do
    +    ~E|<%= hex(value) %>|
    +  end
    +
    +  defp base_value_html(:bytes, value, _no_links) do
    +    ~E|<%= hex(value) %>|
    +  end
    +
    +  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
    new file mode 100644
    index 0000000..ba89600
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/access_helpers.ex
    @@ -0,0 +1,177 @@
    +defmodule BlockScoutWeb.AccessHelpers do
    +  @moduledoc """
    +  Helpers to restrict access to some pages filtering by address
    +  """
    +
    +  import Phoenix.Controller
    +
    +  alias BlockScoutWeb.API.APILogger
    +  alias BlockScoutWeb.API.RPC.RPCView
    +  alias BlockScoutWeb.WebRouter.Helpers
    +  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
    +  end
    +
    +  def get_path(conn, path, template, address_hash) do
    +    basic_args = [conn, template, address_hash]
    +    key = get_restricted_key(conn)
    +    # credo:disable-for-next-line
    +    full_args = if key, do: basic_args ++ [%{:key => key}], else: basic_args
    +
    +    apply(Helpers, path, full_args)
    +  end
    +
    +  def get_path(conn, path, template, address_hash, additional_params) do
    +    basic_args = [conn, template, address_hash]
    +    key = get_restricted_key(conn)
    +    full_additional_params = if key, do: Map.put(additional_params, :key, key), else: additional_params
    +    # credo:disable-for-next-line
    +    full_args = basic_args ++ [full_additional_params]
    +
    +    apply(Helpers, path, full_args)
    +  end
    +
    +  def handle_rate_limit_deny(conn) do
    +    APILogger.message("API rate limit reached")
    +
    +    conn
    +    |> Conn.put_status(429)
    +    |> put_view(RPCView)
    +    |> render(:error, %{error: "429 Too Many Requests"})
    +    |> Conn.halt()
    +  end
    +
    +  def check_rate_limit(conn) do
    +    if Mix.env() == :test do
    +      :ok
    +    else
    +      global_api_rate_limit = Application.get_env(:block_scout_web, :api_rate_limit)[:global_limit]
    +      api_rate_limit_by_key = Application.get_env(:block_scout_web, :api_rate_limit)[:api_rate_limit_by_key]
    +      api_rate_limit_by_ip = Application.get_env(:block_scout_web, :api_rate_limit)[:limit_by_ip]
    +      static_api_key = Application.get_env(:block_scout_web, :api_rate_limit)[:static_api_key]
    +
    +      remote_ip = conn.remote_ip
    +      remote_ip_from_headers = RemoteIp.from(conn.resp_headers)
    +      ip = remote_ip_from_headers || remote_ip
    +      ip_string = to_string(:inet_parse.ntoa(ip))
    +
    +      plan = get_plan(conn.query_params)
    +
    +      cond do
    +        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)
    +
    +        true ->
    +          global_rate_limit(global_api_rate_limit)
    +      end
    +    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} ->
    +        :ok
    +
    +      {:deny, _limit} ->
    +        :rate_limit_reached
    +    end
    +  end
    +
    +  defp rate_limit_by_ip(ip_string, api_rate_limit_by_ip) do
    +    case Hammer.check_rate("api-#{ip_string}", 1_000, api_rate_limit_by_ip) do
    +      {:allow, _count} ->
    +        :ok
    +
    +      {:deny, _limit} ->
    +        :rate_limit_reached
    +    end
    +  end
    +
    +  defp global_rate_limit(global_api_rate_limit) do
    +    case Hammer.check_rate("api", 1_000, global_api_rate_limit) do
    +      {:allow, _count} ->
    +        :ok
    +
    +      {:deny, _limit} ->
    +        :rate_limit_reached
    +    end
    +  end
    +
    +  defp get_restricted_key(%Phoenix.Socket{}) do
    +    nil
    +  end
    +
    +  defp get_restricted_key(conn) do
    +    conn_with_params = Conn.fetch_query_params(conn)
    +    conn_with_params.query_params["key"]
    +  end
    +
    +  defp api_rate_limit_whitelisted_ips do
    +    with api_rate_limit_object <-
    +           :block_scout_web
    +           |> Application.get_env(:api_rate_limit),
    +         {:ok, whitelisted_ips_string} <-
    +           api_rate_limit_object &&
    +             api_rate_limit_object
    +             |> Keyword.fetch(:whitelisted_ips) do
    +      if whitelisted_ips_string, do: String.split(whitelisted_ips_string, ","), else: []
    +    else
    +      _ -> []
    +    end
    +  end
    +end
    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 0000000..0e3a65e
    --- /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 0000000..d97e35f
    --- /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 0000000..9129903
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex
    @@ -0,0 +1,141 @@
    +defmodule BlockScoutWeb.Account.Api.V1.UserView do
    +  alias BlockScoutWeb.Account.Api.V1.AccountView
    +  alias Ecto.Changeset
    +
    +  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
    +    %{
    +      "id" => watchlist.id,
    +      "address_hash" => watchlist.address_hash,
    +      "name" => watchlist.name,
    +      "address_balance" => if(watchlist.fetched_coin_balance, do: watchlist.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
    +    %{
    +      "id" => custom_abi.id,
    +      "contract_address_hash" => 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
    +    %{"id" => address_tag.id, "address_hash" => 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
    +    %{
    +      "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,
    +      "additional_comment" => public_tags_request.additional_comment,
    +      "is_owner" => public_tags_request.is_owner,
    +      "submission_date" => public_tags_request.inserted_at
    +    }
    +  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 0000000..a0b21b7
    --- /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 0000000..cfbeb00
    --- /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 0000000..e296ca9
    --- /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 0000000..3b38eff
    --- /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 0000000..2a13dd8
    --- /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 0000000..74886c3
    --- /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 0000000..7edfa1e
    --- /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 0000000..f3f5383
    --- /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 0000000..fa39663
    --- /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_coin_balance_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_coin_balance_view.ex
    new file mode 100644
    index 0000000..0551d94
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_coin_balance_view.ex
    @@ -0,0 +1,33 @@
    +defmodule BlockScoutWeb.AddressCoinBalanceView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.AccessHelpers
    +  alias Explorer.Chain.Wei
    +
    +  def format(%Wei{} = value) do
    +    format_wei_value(value, :ether)
    +  end
    +
    +  def delta_arrow(value) do
    +    if value.sign == 1 do
    +      "▲"
    +    else
    +      "â–ŧ"
    +    end
    +  end
    +
    +  def delta_sign(value) do
    +    if value.sign == 1 do
    +      "Positive"
    +    else
    +      "Negative"
    +    end
    +  end
    +
    +  def format_delta(%Decimal{} = value) do
    +    value
    +    |> Decimal.abs()
    +    |> Wei.from(:wei)
    +    |> format_wei_value(:ether)
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_common_fields_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_common_fields_view.ex
    new file mode 100644
    index 0000000..9339807
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_common_fields_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.AddressContractVerificationCommonFieldsView do
    +  use BlockScoutWeb, :view
    +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
    new file mode 100644
    index 0000000..3a120c5
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex
    @@ -0,0 +1,6 @@
    +defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain
    +  alias Explorer.Chain.SmartContract
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_json_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_json_view.ex
    new file mode 100644
    index 0000000..79f8681
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_json_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.AddressContractVerificationViaJsonView do
    +  use BlockScoutWeb, :view
    +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 0000000..7ec8e9b
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex
    @@ -0,0 +1,6 @@
    +defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain
    +  alias Explorer.Chain.SmartContract
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex
    new file mode 100644
    index 0000000..cf45efe
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex
    @@ -0,0 +1,6 @@
    +defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain
    +  alias Explorer.Chain.SmartContract
    +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
    new file mode 100644
    index 0000000..385d76c
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_view.ex
    @@ -0,0 +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_verification_vyper_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex
    new file mode 100644
    index 0000000..e0ebba9
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex
    @@ -0,0 +1,6 @@
    +defmodule BlockScoutWeb.AddressContractVerificationVyperView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain
    +  alias Explorer.Chain.SmartContract
    +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
    new file mode 100644
    index 0000000..8856d14
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
    @@ -0,0 +1,151 @@
    +defmodule BlockScoutWeb.AddressContractView do
    +  use BlockScoutWeb, :view
    +
    +  alias ABI.{FunctionSelector, TypeDecoder}
    +  alias Explorer.Chain
    +  alias Explorer.Chain.{Address, Data, InternalTransaction, Transaction}
    +
    +  def render("scripts.html", %{conn: conn}) do
    +    render_scripts(conn, "address_contract/code_highlighting.js")
    +  end
    +
    +  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.
    +
    +    iex> BlockScoutWeb.AddressContractView.format_optimization_text(true)
    +    "true"
    +
    +    iex> BlockScoutWeb.AddressContractView.format_optimization_text(false)
    +    "false"
    +  """
    +  def format_optimization_text(true), do: gettext("true")
    +  def format_optimization_text(false), do: gettext("false")
    +
    +  def format_constructor_arguments(contract, conn) do
    +    constructor_abi = Enum.find(contract.abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end)
    +
    +    input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1)
    +
    +    {_, result} =
    +      contract.constructor_arguments
    +      |> decode_data(input_types)
    +      |> Enum.zip(constructor_abi["inputs"])
    +      |> Enum.reduce({0, "#{contract.constructor_arguments}\n\n"}, fn {val, %{"type" => type}}, {count, acc} ->
    +        formatted_val = val_to_string(val, type, conn)
    +
    +        {count + 1, "#{acc}Arg [#{count}] (#{type}) : #{formatted_val}\n"}
    +      end)
    +
    +    result
    +  rescue
    +    _ -> contract.constructor_arguments
    +  end
    +
    +  defp val_to_string(val, type, conn) do
    +    cond do
    +      type =~ "[]" ->
    +        if is_list(val) or is_tuple(val) do
    +          "[" <>
    +            Enum.map_join(val, ", ", fn el -> val_to_string(el, String.replace_suffix(type, "[]", ""), conn) end) <> "]"
    +        else
    +          to_string(val)
    +        end
    +
    +      type =~ "address" ->
    +        address_hash = "0x" <> Base.encode16(val, case: :lower)
    +
    +        address = get_address(address_hash)
    +
    +        get_formatted_address_data(address, address_hash, conn)
    +
    +      type =~ "bytes" ->
    +        Base.encode16(val, case: :lower)
    +
    +      true ->
    +        to_string(val)
    +    end
    +  end
    +
    +  defp get_address(address_hash) do
    +    case Chain.string_to_address_hash(address_hash) do
    +      {:ok, address} -> address
    +      _ -> nil
    +    end
    +  end
    +
    +  defp get_formatted_address_data(address, address_hash, conn) do
    +    if address != nil do
    +      "" <> address_hash <> ""
    +    else
    +      address_hash
    +    end
    +  end
    +
    +  defp decode_data("0x" <> encoded_data, types) do
    +    decode_data(encoded_data, types)
    +  end
    +
    +  defp decode_data(encoded_data, types) do
    +    encoded_data
    +    |> Base.decode16!(case: :mixed)
    +    |> TypeDecoder.decode_raw(types)
    +  end
    +
    +  def format_external_libraries(libraries, conn) do
    +    Enum.reduce(libraries, "", fn %{name: name, address_hash: address_hash}, acc ->
    +      address = get_address(address_hash)
    +      "#{acc}#{name} : #{get_formatted_address_data(address, address_hash, conn)}  \n"
    +    end)
    +  end
    +
    +  def contract_lines_with_index(source_code) do
    +    contract_lines =
    +      source_code
    +      |> String.split("\n")
    +
    +    max_digits =
    +      contract_lines
    +      |> Enum.count()
    +      |> Integer.digits()
    +      |> Enum.count()
    +
    +    contract_lines
    +    |> Enum.with_index(1)
    +    |> Enum.map(fn {value, line} ->
    +      {value, String.pad_leading(to_string(line), max_digits, " ")}
    +    end)
    +  end
    +
    +  def contract_creation_code(%Address{
    +        contract_code: %Data{bytes: <<>>},
    +        contracts_creation_internal_transaction: %InternalTransaction{init: init}
    +      }) do
    +    {:selfdestructed, init}
    +  end
    +
    +  def contract_creation_code(%Address{contract_code: contract_code}) do
    +    {:ok, contract_code}
    +  end
    +
    +  def creation_code(%Address{contracts_creation_internal_transaction: %InternalTransaction{}} = address) do
    +    address.contracts_creation_internal_transaction.input
    +  end
    +
    +  def creation_code(%Address{contracts_creation_transaction: %Transaction{}} = address) do
    +    address.contracts_creation_transaction.input
    +  end
    +
    +  def creation_code(%Address{contracts_creation_transaction: nil}) do
    +    nil
    +  end
    +
    +  def sourcify_repo_url(address_hash, partial_match) do
    +    checksummed_hash = Address.checksum(address_hash)
    +    chain_id = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:chain_id]
    +    repo_url = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:repo_url]
    +    match = if partial_match, do: "/partial_match/", else: "/full_match/"
    +    repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/"
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex
    new file mode 100644
    index 0000000..4e06e37
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex
    @@ -0,0 +1,272 @@
    +defmodule BlockScoutWeb.AddressDecompiledContractView do
    +  use BlockScoutWeb, :view
    +
    +  @colors %{
    +    "\e[95m" => "",
    +    # red
    +    "\e[91m" => "",
    +    # gray
    +    "\e[38;5;8m" => "",
    +    # green
    +    "\e[32m" => "",
    +    # yellowgreen
    +    "\e[93m" => "",
    +    # yellow
    +    "\e[92m" => "",
    +    # red
    +    "\e[94m" => ""
    +  }
    +
    +  @comment_start "#"
    +
    +  @reserved_words_types [
    +    "var",
    +    "bool",
    +    "string",
    +    "int",
    +    "uint",
    +    "int8",
    +    "uint8",
    +    "int16",
    +    "uint16",
    +    "int24",
    +    "uint24",
    +    "int32",
    +    "uint32",
    +    "int40",
    +    "uint40",
    +    "int48",
    +    "uint48",
    +    "int56",
    +    "uint56",
    +    "int64",
    +    "uint64",
    +    "int72",
    +    "uint72",
    +    "int80",
    +    "uint80",
    +    "int88",
    +    "uint88",
    +    "int96",
    +    "uint96",
    +    "int104",
    +    "uint104",
    +    "int112",
    +    "uint112",
    +    "int120",
    +    "uint120",
    +    "int128",
    +    "uint128",
    +    "int136",
    +    "uint136",
    +    "int144",
    +    "uint144",
    +    "int152",
    +    "uint152",
    +    "int160",
    +    "uint160",
    +    "int168",
    +    "uint168",
    +    "int176",
    +    "uint176",
    +    "int184",
    +    "uint184",
    +    "int192",
    +    "uint192",
    +    "int200",
    +    "uint200",
    +    "int208",
    +    "uint208",
    +    "int216",
    +    "uint216",
    +    "int224",
    +    "uint224",
    +    "int232",
    +    "uint232",
    +    "int240",
    +    "uint240",
    +    "int248",
    +    "uint248",
    +    "int256",
    +    "uint256",
    +    "byte",
    +    "bytes",
    +    "bytes1",
    +    "bytes2",
    +    "bytes3",
    +    "bytes4",
    +    "bytes5",
    +    "bytes6",
    +    "bytes7",
    +    "bytes8",
    +    "bytes9",
    +    "bytes10",
    +    "bytes11",
    +    "bytes12",
    +    "bytes13",
    +    "bytes14",
    +    "bytes15",
    +    "bytes16",
    +    "bytes17",
    +    "bytes18",
    +    "bytes19",
    +    "bytes20",
    +    "bytes21",
    +    "bytes22",
    +    "bytes23",
    +    "bytes24",
    +    "bytes25",
    +    "bytes26",
    +    "bytes27",
    +    "bytes28",
    +    "bytes29",
    +    "bytes30",
    +    "bytes31",
    +    "bytes32",
    +    "true",
    +    "false",
    +    "enum",
    +    "struct",
    +    "mapping",
    +    "address"
    +  ]
    +
    +  @reserved_words_keywords [
    +    "def",
    +    "require",
    +    "revert",
    +    "return",
    +    "assembly",
    +    "memory",
    +    "mem"
    +  ]
    +
    +  @modifiers [
    +    "payable",
    +    "public",
    +    "view",
    +    "pure",
    +    "returns",
    +    "internal"
    +  ]
    +
    +  @reserved_words @reserved_words_keywords ++ @reserved_words_types
    +
    +  @reserved_words_regexp ([@comment_start | @reserved_words] ++ @modifiers)
    +                         |> Enum.reduce("", fn el, acc -> acc <> "|" <> el end)
    +                         |> Regex.compile!()
    +
    +  def highlight_decompiled_code(code) do
    +    {_, result} =
    +      @colors
    +      |> Enum.reduce(code, fn {symbol, rgb}, acc ->
    +        String.replace(acc, symbol, rgb)
    +      end)
    +      |> String.replace("\e[1m", "")
    +      |> String.replace("Âģ", "»")
    +      |> String.replace("\e[0m", "")
    +      |> String.split(~r/\|\|\<\/span\>/,
    +        include_captures: true,
    +        trim: true
    +      )
    +      |> add_styles_to_every_line()
    +
    +    result
    +    |> Enum.reduce("", fn part, acc ->
    +      part <> acc
    +    end)
    +    |> add_styles_to_reserved_words()
    +    |> add_line_numbers()
    +  end
    +
    +  defp add_styles_to_every_line(lines) do
    +    lines
    +    |> Enum.reduce({"", []}, fn part, {style, acc} ->
    +      new_style =
    +        cond do
    +          String.contains?(part, " part
    +          part == "" -> ""
    +          true -> style
    +        end
    +
    +      new_part = new_part(part, new_style)
    +
    +      {new_style, [new_part | acc]}
    +    end)
    +  end
    +
    +  defp add_styles_to_reserved_words(code) do
    +    code
    +    |> String.split("\n")
    +    |> Enum.map(fn line ->
    +      add_styles_to_line(line)
    +    end)
    +    |> Enum.reduce("", fn el, acc ->
    +      acc <> el <> "\n"
    +    end)
    +  end
    +
    +  defp add_styles_to_line(line) do
    +    parts =
    +      line
    +      |> String.split(@reserved_words_regexp,
    +        include_captures: true
    +      )
    +
    +    comment_position = Enum.find_index(parts, fn part -> part == "#" end)
    +
    +    parts
    +    |> Enum.with_index()
    +    |> Enum.map(fn {el, index} ->
    +      cond do
    +        !(is_nil(comment_position) || comment_position > index) -> el
    +        el in @reserved_words -> "" <> el <> ""
    +        el in @modifiers -> "" <> el <> ""
    +        true -> el
    +      end
    +    end)
    +    |> Enum.reduce("", fn el, acc ->
    +      acc <> el
    +    end)
    +  end
    +
    +  def last_decompiled_contract_version(decompiled_contracts) when is_nil(decompiled_contracts), do: nil
    +
    +  def last_decompiled_contract_version(decompiled_contracts) when decompiled_contracts == [], do: nil
    +
    +  def last_decompiled_contract_version(decompiled_contracts) do
    +    Enum.max_by(decompiled_contracts, & &1.decompiler_version)
    +  end
    +
    +  defp add_line_numbers(code) do
    +    code
    +    |> String.split("\n")
    +    |> Enum.reduce("", fn line, acc ->
    +      acc <> "#{line}\n"
    +    end)
    +  end
    +
    +  defp new_part(part, new_style) do
    +    cond do
    +      part == "" ->
    +        ""
    +
    +      part == "" ->
    +        ""
    +
    +      part == new_style ->
    +        ""
    +
    +      new_style == "" ->
    +        part
    +
    +      true ->
    +        part
    +        |> String.split("\n")
    +        |> Enum.reduce("", fn p, a ->
    +          a <> new_style <> p <> "\n"
    +        end)
    +        |> String.slice(0..-2)
    +    end
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_internal_transaction_view.ex
    new file mode 100644
    index 0000000..092e223
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_internal_transaction_view.ex
    @@ -0,0 +1,14 @@
    +defmodule BlockScoutWeb.AddressInternalTransactionView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.AccessHelpers
    +  alias Explorer.Chain.Address
    +
    +  def format_current_filter(filter) do
    +    case filter do
    +      "to" -> gettext("To")
    +      "from" -> gettext("From")
    +      _ -> gettext("All")
    +    end
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
    new file mode 100644
    index 0000000..577661a
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
    @@ -0,0 +1,9 @@
    +defmodule BlockScoutWeb.AddressLogsView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain.{Address, Log}
    +
    +  def decode(log, transaction) do
    +    Log.decode(log, transaction)
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
    new file mode 100644
    index 0000000..559af20
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.AddressReadContractView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
    new file mode 100644
    index 0000000..e51247f
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.AddressReadProxyView do
    +  use BlockScoutWeb, :view
    +end
    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
    new file mode 100644
    index 0000000..d070ddf
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex
    @@ -0,0 +1,133 @@
    +defmodule BlockScoutWeb.AddressTokenBalanceView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.AccessHelpers
    +  alias Explorer.Chain
    +  alias Explorer.Chain.Address
    +  alias Explorer.Counters.AddressTokenUsdSum
    +
    +  def tokens_count_title(token_balances) do
    +    ngettext("%{count} token", "%{count} tokens", Enum.count(token_balances))
    +  end
    +
    +  def filter_by_type(token_balances, type) do
    +    Enum.filter(token_balances, fn {_token_balance, token} -> token.type == type end)
    +  end
    +
    +  @doc """
    +  Sorts the given list of tokens in alphabetically order considering nil values in the bottom of
    +  the list.
    +  """
    +  def sort_by_name(token_balances) do
    +    {unnamed, named} = Enum.split_with(token_balances, &is_nil(&1.token.name))
    +    Enum.sort_by(named, &String.downcase(&1.token.name)) ++ unnamed
    +  end
    +
    +  @doc """
    +  Sorts the given list of tokens by usd_value of token in descending order and alphabetically order considering nil values in the bottom of
    +  the list.
    +  """
    +  def sort_by_usd_value_and_name(token_balances) do
    +    token_balances
    +    |> Enum.sort(fn {token_balance1, token1}, {token_balance2, token2} ->
    +      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,
    +        token1,
    +        token2
    +      )
    +    end)
    +  end
    +
    +  defp sort_2_tokens_by_name(token_name1, token_name2) do
    +    cond do
    +      token_name1 && token_name2 ->
    +        String.downcase(token_name1) <= String.downcase(token_name2)
    +
    +      token_name1 && is_nil(token_name2) ->
    +        true
    +
    +      is_nil(token_name1) && token_name2 ->
    +        false
    +
    +      true ->
    +        true
    +    end
    +  end
    +
    +  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, token1), Chain.balance_in_usd(token_balance2, token2)) do
    +      :gt ->
    +        true
    +
    +      :eq ->
    +        sort_by_name
    +
    +      :lt ->
    +        false
    +    end
    +  end
    +
    +  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,
    +         _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,
    +         _token1,
    +         _token2
    +       )
    +       when is_nil(usd_value1) and is_nil(usd_value2) do
    +    sort_by_name
    +  end
    +
    +  def address_tokens_usd_sum_cache(address, token_balances) do
    +    AddressTokenUsdSum.fetch(address, token_balances)
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_token_transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_token_transfer_view.ex
    new file mode 100644
    index 0000000..ca82caa
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_token_transfer_view.ex
    @@ -0,0 +1,14 @@
    +defmodule BlockScoutWeb.AddressTokenTransferView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.AccessHelpers
    +  alias Explorer.Chain.Address
    +
    +  def format_current_filter(filter) do
    +    case filter do
    +      "to" -> gettext("To")
    +      "from" -> gettext("From")
    +      _ -> gettext("All")
    +    end
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex
    new file mode 100644
    index 0000000..83cdf79
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex
    @@ -0,0 +1,7 @@
    +defmodule BlockScoutWeb.AddressTokenView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.{AddressView, ChainView}
    +  alias Explorer.Chain
    +  alias Explorer.Chain.{Address, Wei}
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex
    new file mode 100644
    index 0000000..64ba591
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex
    @@ -0,0 +1,14 @@
    +defmodule BlockScoutWeb.AddressTransactionView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.AccessHelpers
    +  alias Explorer.Chain.Address
    +
    +  def format_current_filter(filter) do
    +    case filter do
    +      "to" -> gettext("To")
    +      "from" -> gettext("From")
    +      _ -> gettext("All")
    +    end
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_validation_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_validation_view.ex
    new file mode 100644
    index 0000000..65f2bb0
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_validation_view.ex
    @@ -0,0 +1,5 @@
    +defmodule BlockScoutWeb.AddressValidationView do
    +  use BlockScoutWeb, :view
    +
    +  # import BlockScoutWeb.AddressView, only: [contract?: 1, smart_contract_verified?: 1]
    +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
    new file mode 100644
    index 0000000..8fff82f
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
    @@ -0,0 +1,481 @@
    +defmodule BlockScoutWeb.AddressView do
    +  use BlockScoutWeb, :view
    +
    +  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 [
    +    "coin-balances",
    +    "contracts",
    +    "decompiled-contracts",
    +    "internal-transactions",
    +    "token-transfers",
    +    "read-contract",
    +    "read-proxy",
    +    "write-contract",
    +    "write-proxy",
    +    "tokens",
    +    "transactions",
    +    "validations"
    +  ]
    +
    +  def address_partial_selector(struct_to_render_from, direction, current_address, truncate \\ false)
    +
    +  def address_partial_selector(%Address{} = address, _, current_address, truncate) do
    +    matching_address_check(current_address, address, contract?(address), truncate)
    +  end
    +
    +  def address_partial_selector(
    +        %InternalTransaction{to_address_hash: nil, created_contract_address_hash: nil},
    +        :to,
    +        _current_address,
    +        _truncate
    +      ) do
    +    gettext("Contract Address Pending")
    +  end
    +
    +  def address_partial_selector(
    +        %InternalTransaction{to_address: nil, created_contract_address: contract_address},
    +        :to,
    +        current_address,
    +        truncate
    +      ) do
    +    matching_address_check(current_address, contract_address, true, truncate)
    +  end
    +
    +  def address_partial_selector(%InternalTransaction{to_address: address}, :to, current_address, truncate) do
    +    matching_address_check(current_address, address, contract?(address), truncate)
    +  end
    +
    +  def address_partial_selector(%InternalTransaction{from_address: address}, :from, current_address, truncate) do
    +    matching_address_check(current_address, address, contract?(address), truncate)
    +  end
    +
    +  def address_partial_selector(%TokenTransfer{to_address: address}, :to, current_address, truncate) do
    +    matching_address_check(current_address, address, contract?(address), truncate)
    +  end
    +
    +  def address_partial_selector(%TokenTransfer{from_address: address}, :from, current_address, truncate) do
    +    matching_address_check(current_address, address, contract?(address), truncate)
    +  end
    +
    +  def address_partial_selector(
    +        %Transaction{to_address_hash: nil, created_contract_address_hash: nil},
    +        :to,
    +        _current_address,
    +        _truncate
    +      ) do
    +    gettext("Contract Address Pending")
    +  end
    +
    +  def address_partial_selector(
    +        %Transaction{to_address: nil, created_contract_address: contract_address},
    +        :to,
    +        current_address,
    +        truncate
    +      ) do
    +    matching_address_check(current_address, contract_address, true, truncate)
    +  end
    +
    +  def address_partial_selector(%Transaction{to_address: address}, :to, current_address, truncate) do
    +    matching_address_check(current_address, address, contract?(address), truncate)
    +  end
    +
    +  def address_partial_selector(%Transaction{from_address: address}, :from, current_address, truncate) do
    +    matching_address_check(current_address, address, contract?(address), truncate)
    +  end
    +
    +  def address_partial_selector(%Reward{address: address}, _, current_address, truncate) do
    +    matching_address_check(current_address, address, false, truncate)
    +  end
    +
    +  def address_title(%Address{} = address) do
    +    if contract?(address) do
    +      gettext("Contract Address")
    +    else
    +      gettext("Address")
    +    end
    +  end
    +
    +  @doc """
    +  Returns a formatted address balance and includes the unit.
    +  """
    +  def balance(%Address{fetched_coin_balance: nil}), do: ""
    +
    +  def balance(%Address{fetched_coin_balance: balance}) do
    +    format_wei_value(balance, :ether)
    +  end
    +
    +  def balance_percentage_enabled?(total_supply) do
    +    Application.get_env(:block_scout_web, :show_percentage) && total_supply > 0
    +  end
    +
    +  def balance_percentage(_, nil), do: ""
    +
    +  def balance_percentage(
    +        %Address{
    +          hash: %Explorer.Chain.Hash{
    +            byte_count: 20,
    +            bytes: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>
    +          }
    +        },
    +        _
    +      ),
    +      do: ""
    +
    +  def balance_percentage(%Address{fetched_coin_balance: balance}, total_supply) do
    +    if Decimal.compare(total_supply, 0) == :gt do
    +      balance
    +      |> Wei.to(:ether)
    +      |> Decimal.div(Decimal.new(total_supply))
    +      |> Decimal.mult(100)
    +      |> Decimal.round(4)
    +      |> Decimal.to_string(:normal)
    +      |> Kernel.<>("% #{gettext("Market Cap")}")
    +    else
    +      balance
    +      |> Wei.to(:ether)
    +      |> Decimal.to_string(:normal)
    +    end
    +  end
    +
    +  def empty_exchange_rate?(exchange_rate) do
    +    TokenExchangeRate.null?(exchange_rate)
    +  end
    +
    +  def balance_percentage(%Address{fetched_coin_balance: _} = address) do
    +    balance_percentage(address, Chain.total_supply())
    +  end
    +
    +  def balance_block_number(%Address{fetched_coin_balance_block_number: nil}), do: ""
    +
    +  def balance_block_number(%Address{fetched_coin_balance_block_number: fetched_coin_balance_block_number}) do
    +    to_string(fetched_coin_balance_block_number)
    +  end
    +
    +  def contract?(%Address{contract_code: nil}), do: false
    +
    +  def contract?(%Address{contract_code: _}), do: true
    +
    +  def contract?(nil), do: true
    +
    +  def validator?(val) when val > 0, do: true
    +
    +  def validator?(_), do: false
    +
    +  def hash(%Address{hash: hash}) do
    +    to_string(hash)
    +  end
    +
    +  @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
    +    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 primary_name(%Address{names: _} = address, false) do
    +    primary_name(Repo.preload(address, [:names]), true)
    +  end
    +
    +  def primary_name(%Address{names: _}, true), do: nil
    +
    +  def implementation_name(%Address{smart_contract: %{implementation_name: implementation_name}}),
    +    do: implementation_name
    +
    +  def implementation_name(_), do: nil
    +
    +  def primary_validator_metadata(%Address{names: [_ | _] = address_names}) do
    +    case Enum.find(address_names, &(&1.primary == true)) do
    +      %Address.Name{
    +        metadata:
    +          metadata = %{
    +            "license_id" => _,
    +            "address" => _,
    +            "state" => _,
    +            "zipcode" => _,
    +            "expiration_date" => _,
    +            "created_date" => _
    +          }
    +      } ->
    +        metadata
    +
    +      _ ->
    +        nil
    +    end
    +  end
    +
    +  def primary_validator_metadata(%Address{names: _}), do: nil
    +
    +  def format_datetime_string(unix_date) do
    +    unix_date
    +    |> DateTime.from_unix!()
    +    |> Timex.format!("{M}-{D}-{YYYY}")
    +  end
    +
    +  def qr_code(address_hash) do
    +    address_hash
    +    |> to_string()
    +    |> QRCode.to_png()
    +    |> Base.encode64()
    +  end
    +
    +  def smart_contract_verified?(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false
    +
    +  def smart_contract_verified?(%Address{smart_contract: %SmartContract{}}), do: true
    +
    +  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))
    +  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)
    +  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,
    +      &Writer.write_function?(&1)
    +    )
    +  end
    +
    +  def smart_contract_with_write_functions?(%Address{smart_contract: nil}), do: false
    +
    +  def has_decompiled_code?(address) do
    +    address.has_decompiled_code? ||
    +      (Ecto.assoc_loaded?(address.decompiled_smart_contracts) && Enum.count(address.decompiled_smart_contracts) > 0)
    +  end
    +
    +  def token_title(%Token{name: nil, contract_address_hash: contract_address_hash}) do
    +    short_hash_left_right(contract_address_hash)
    +  end
    +
    +  def token_title(%Token{name: name, symbol: symbol}), do: "#{name} (#{symbol})"
    +
    +  def trimmed_hash(%Hash{} = hash) do
    +    string_hash = to_string(hash)
    +    trimmed_hash(string_hash)
    +  end
    +
    +  def trimmed_hash(address) when is_binary(address) do
    +    "#{String.slice(address, 0..7)}–#{String.slice(address, -6..-1)}"
    +  end
    +
    +  def trimmed_hash(_), do: ""
    +
    +  def trimmed_verify_link(hash) do
    +    string_hash = to_string(hash)
    +    "#{String.slice(string_hash, 0..21)}..."
    +  end
    +
    +  def transaction_hash(%Address{contracts_creation_internal_transaction: %InternalTransaction{}} = address) do
    +    address.contracts_creation_internal_transaction.transaction_hash
    +  end
    +
    +  def transaction_hash(%Address{contracts_creation_transaction: %Transaction{}} = address) do
    +    address.contracts_creation_transaction.hash
    +  end
    +
    +  def from_address_hash(%Address{contracts_creation_internal_transaction: %InternalTransaction{}} = address) do
    +    address.contracts_creation_internal_transaction.from_address_hash
    +  end
    +
    +  def from_address_hash(%Address{contracts_creation_transaction: %Transaction{}} = address) do
    +    address.contracts_creation_transaction.from_address_hash
    +  end
    +
    +  def from_address_hash(_address), do: nil
    +
    +  def address_link_to_other_explorer(link, address, full) do
    +    if full do
    +      link <> to_string(address)
    +    else
    +      trimmed_verify_link(link <> to_string(address))
    +    end
    +  end
    +
    +  defp matching_address_check(%Address{hash: hash} = current_address, %Address{hash: hash}, contract?, truncate) do
    +    [
    +      view_module: __MODULE__,
    +      partial: "_responsive_hash.html",
    +      address: current_address,
    +      contract: contract?,
    +      truncate: truncate,
    +      use_custom_tooltip: false
    +    ]
    +  end
    +
    +  defp matching_address_check(_current_address, %Address{} = address, contract?, truncate) do
    +    [
    +      view_module: __MODULE__,
    +      partial: "_link.html",
    +      address: address,
    +      contract: contract?,
    +      truncate: truncate,
    +      use_custom_tooltip: false
    +    ]
    +  end
    +
    +  @doc """
    +  Get the current tab name/title from the request path and possible tab names.
    +
    +  The tabs on mobile are represented by a dropdown list, which has a title. This title is the
    +  currently selected tab name. This function returns that name, properly gettext'ed.
    +
    +  The list of possible tab names for this page is represented by the attribute @tab.
    +
    +  Raises error if there is no match, so a developer of a new tab must include it in the list.
    +  """
    +  def current_tab_name(request_path) do
    +    @tabs
    +    |> Enum.filter(&tab_active?(&1, request_path))
    +    |> tab_name()
    +  end
    +
    +  defp tab_name(["tokens"]), do: gettext("Tokens")
    +  defp tab_name(["internal-transactions"]), do: gettext("Internal Transactions")
    +  defp tab_name(["transactions"]), do: gettext("Transactions")
    +  defp tab_name(["token-transfers"]), do: gettext("Token Transfers")
    +  defp tab_name(["contracts"]), do: gettext("Code")
    +  defp tab_name(["decompiled-contracts"]), do: gettext("Decompiled Code")
    +  defp tab_name(["read-contract"]), do: gettext("Read Contract")
    +  defp tab_name(["read-proxy"]), do: gettext("Read Proxy")
    +  defp tab_name(["write-contract"]), do: gettext("Write Contract")
    +  defp tab_name(["write-proxy"]), do: gettext("Write Proxy")
    +  defp tab_name(["coin-balances"]), do: gettext("Coin Balance History")
    +  defp tab_name(["validations"]), do: gettext("Blocks Validated")
    +  defp tab_name(["logs"]), do: gettext("Logs")
    +
    +  def short_hash(%Address{hash: hash}) do
    +    <<
    +      "0x",
    +      short_address::binary-size(6),
    +      _rest::binary
    +    >> = to_string(hash)
    +
    +    "0x" <> short_address
    +  end
    +
    +  def short_hash_left_right(hash) when not is_nil(hash) do
    +    case hash do
    +      "0x" <> rest ->
    +        shortify_hash_string(rest)
    +
    +      %Chain.Hash{
    +        byte_count: _,
    +        bytes: bytes
    +      } ->
    +        shortify_hash_string(Base.encode16(bytes, case: :lower))
    +
    +      hash ->
    +        shortify_hash_string(hash)
    +    end
    +  end
    +
    +  def short_hash_left_right(hash) when is_nil(hash), do: ""
    +
    +  defp shortify_hash_string(hash) do
    +    <<
    +      left::binary-size(6),
    +      _middle::binary-size(28),
    +      right::binary-size(6)
    +    >> = to_string(hash)
    +
    +    "0x" <> left <> "-" <> right
    +  end
    +
    +  def short_contract_name(name, max_length) do
    +    short_string(name, max_length)
    +  end
    +
    +  def short_token_id(%Decimal{} = token_id, max_length) do
    +    token_id
    +    |> Decimal.to_string()
    +    |> short_string(max_length)
    +  end
    +
    +  def short_token_id(token_id, max_length) do
    +    short_string(token_id, max_length)
    +  end
    +
    +  def short_string(nil, _max_length), do: ""
    +
    +  def short_string(name, max_length) do
    +    part_length = Kernel.trunc(max_length / 4)
    +
    +    if String.length(name) <= max_length,
    +      do: name,
    +      else: "#{String.slice(name, 0, max_length - part_length)}..#{String.slice(name, -part_length, part_length)}"
    +  end
    +
    +  def address_page_title(address) do
    +    cond do
    +      smart_contract_verified?(address) -> "#{address.smart_contract.name} (#{to_string(address)})"
    +      contract?(address) -> "Contract #{to_string(address)}"
    +      true -> "#{to_string(address)}"
    +    end
    +  end
    +
    +  def smart_contract_is_gnosis_safe_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
    +    address.smart_contract.name == "GnosisSafeProxy" && Chain.gnosis_safe_contract?(address.smart_contract.abi)
    +  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/address_write_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex
    new file mode 100644
    index 0000000..c21e1f5
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.AddressWriteContractView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex
    new file mode 100644
    index 0000000..17634e9
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.AddressWriteProxyView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/admin/dashboard_view.ex b/apps/block_scout_web/lib/block_scout_web/views/admin/dashboard_view.ex
    new file mode 100644
    index 0000000..536ef37
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/admin/dashboard_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.Admin.DashboardView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/admin/session_view.ex b/apps/block_scout_web/lib/block_scout_web/views/admin/session_view.ex
    new file mode 100644
    index 0000000..6e99ff6
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/admin/session_view.ex
    @@ -0,0 +1,7 @@
    +defmodule BlockScoutWeb.Admin.SessionView do
    +  use BlockScoutWeb, :view
    +
    +  import BlockScoutWeb.AdminRouter.Helpers
    +
    +  alias BlockScoutWeb.FormView
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/admin/setup_view.ex b/apps/block_scout_web/lib/block_scout_web/views/admin/setup_view.ex
    new file mode 100644
    index 0000000..75f8a36
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/admin/setup_view.ex
    @@ -0,0 +1,7 @@
    +defmodule BlockScoutWeb.Admin.SetupView do
    +  use BlockScoutWeb, :view
    +
    +  import BlockScoutWeb.AdminRouter.Helpers
    +
    +  alias BlockScoutWeb.FormView
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/advertisement/banners_ad_view.ex b/apps/block_scout_web/lib/block_scout_web/views/advertisement/banners_ad_view.ex
    new file mode 100644
    index 0000000..47f461f
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/advertisement/banners_ad_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.Advertisement.BannersAdView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/advertisement/text_ad_view.ex b/apps/block_scout_web/lib/block_scout_web/views/advertisement/text_ad_view.ex
    new file mode 100644
    index 0000000..8d73eb3
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/advertisement/text_ad_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.Advertisement.TextAdView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex
    new file mode 100644
    index 0000000..16f85b3
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex
    @@ -0,0 +1,84 @@
    +defmodule BlockScoutWeb.API.EthRPC.View do
    +  @moduledoc """
    +  Views for /eth-rpc API endpoints
    +  """
    +  use BlockScoutWeb, :view
    +
    +  defstruct [:result, :id, :error]
    +
    +  def render("show.json", %{result: result, id: id}) do
    +    %__MODULE__{
    +      result: result,
    +      id: id
    +    }
    +  end
    +
    +  def render("error.json", %{error: message, id: id}) do
    +    %__MODULE__{
    +      error: message,
    +      id: id
    +    }
    +  end
    +
    +  def render("response.json", %{response: %{error: error, id: id}}) do
    +    %__MODULE__{
    +      error: error,
    +      id: id
    +    }
    +  end
    +
    +  def render("response.json", %{response: %{result: result, id: id}}) do
    +    %__MODULE__{
    +      result: result,
    +      id: id
    +    }
    +  end
    +
    +  def render("responses.json", %{responses: responses}) do
    +    Enum.map(responses, fn
    +      %{error: error, id: id} ->
    +        %__MODULE__{
    +          error: error,
    +          id: id
    +        }
    +
    +      %{result: result, id: id} ->
    +        %__MODULE__{
    +          result: result,
    +          id: id
    +        }
    +    end)
    +  end
    +
    +  defimpl Poison.Encoder, for: BlockScoutWeb.API.EthRPC.View do
    +    def encode(%BlockScoutWeb.API.EthRPC.View{result: result, id: id, error: error}, _options) when is_nil(error) do
    +      result = Poison.encode!(result)
    +
    +      """
    +      {"jsonrpc":"2.0","result":#{result},"id":#{id}}
    +      """
    +    end
    +
    +    def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) do
    +      """
    +      {"jsonrpc":"2.0","error": "#{error}","id": #{id}}
    +      """
    +    end
    +  end
    +
    +  defimpl Jason.Encoder, for: BlockScoutWeb.API.EthRPC.View do
    +    def encode(%BlockScoutWeb.API.EthRPC.View{result: result, id: id, error: error}, _options) when is_nil(error) do
    +      result = Jason.encode!(result)
    +
    +      """
    +      {"jsonrpc":"2.0","result":#{result},"id":#{id}}
    +      """
    +    end
    +
    +    def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) do
    +      """
    +      {"jsonrpc":"2.0","error": "#{error}","id": #{id}}
    +      """
    +    end
    +  end
    +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
    new file mode 100644
    index 0000000..1d407c4
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
    @@ -0,0 +1,215 @@
    +defmodule BlockScoutWeb.API.RPC.AddressView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.API.EthRPC.View, as: EthRPCView
    +  alias BlockScoutWeb.API.RPC.RPCView
    +
    +  def render("listaccounts.json", %{accounts: accounts}) do
    +    accounts = Enum.map(accounts, &prepare_account/1)
    +    RPCView.render("show.json", data: accounts)
    +  end
    +
    +  def render("balance.json", %{addresses: [address]}) do
    +    RPCView.render("show.json", data: balance(address))
    +  end
    +
    +  def render("balance.json", assigns) do
    +    render("balancemulti.json", assigns)
    +  end
    +
    +  def render("balancemulti.json", %{addresses: addresses}) do
    +    data = Enum.map(addresses, &render_address/1)
    +
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("pendingtxlist.json", %{transactions: transactions}) do
    +    data = Enum.map(transactions, &prepare_pending_transaction/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("txlist.json", %{transactions: transactions}) do
    +    data = Enum.map(transactions, &prepare_transaction/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("txlistinternal.json", %{internal_transactions: internal_transactions}) do
    +    data = Enum.map(internal_transactions, &prepare_internal_transaction/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("tokentx.json", %{token_transfers: token_transfers}) do
    +    data = Enum.map(token_transfers, &prepare_token_transfer/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("tokenbalance.json", %{token_balance: token_balance}) do
    +    RPCView.render("show.json", data: to_string(token_balance))
    +  end
    +
    +  def render("token_list.json", %{token_list: token_list}) do
    +    data = Enum.map(token_list, &prepare_token/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("getminedblocks.json", %{blocks: blocks}) do
    +    data = Enum.map(blocks, &prepare_block/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("eth_get_balance.json", %{balance: balance}) do
    +    EthRPCView.render("show.json", %{result: balance, id: 0})
    +  end
    +
    +  def render("eth_get_balance_error.json", %{error: message}) do
    +    EthRPCView.render("error.json", %{error: message, id: 0})
    +  end
    +
    +  def render("error.json", assigns) do
    +    RPCView.render("error.json", assigns)
    +  end
    +
    +  defp render_address(address) do
    +    %{
    +      "account" => "#{address.hash}",
    +      "balance" => balance(address),
    +      "stale" => address.stale? || false
    +    }
    +  end
    +
    +  defp prepare_account(address) do
    +    %{
    +      "balance" => to_string(address.fetched_coin_balance && address.fetched_coin_balance.value),
    +      "address" => to_string(address.hash),
    +      "stale" => address.stale? || false
    +    }
    +  end
    +
    +  defp prepare_pending_transaction(transaction) do
    +    %{
    +      "hash" => "#{transaction.hash}",
    +      "nonce" => "#{transaction.nonce}",
    +      "from" => "#{transaction.from_address_hash}",
    +      "to" => "#{transaction.to_address_hash}",
    +      "value" => "#{transaction.value.value}",
    +      "gas" => "#{transaction.gas}",
    +      "gasPrice" => "#{transaction.gas_price.value}",
    +      "input" => "#{transaction.input}",
    +      "contractAddress" => "#{transaction.created_contract_address_hash}",
    +      "cumulativeGasUsed" => "#{transaction.cumulative_gas_used}",
    +      "gasUsed" => "#{transaction.gas_used}"
    +    }
    +  end
    +
    +  defp prepare_transaction(transaction) do
    +    %{
    +      "blockNumber" => "#{transaction.block_number}",
    +      "timeStamp" => "#{DateTime.to_unix(transaction.block_timestamp)}",
    +      "hash" => "#{transaction.hash}",
    +      "nonce" => "#{transaction.nonce}",
    +      "blockHash" => "#{transaction.block_hash}",
    +      "transactionIndex" => "#{transaction.index}",
    +      "from" => "#{transaction.from_address_hash}",
    +      "to" => "#{transaction.to_address_hash}",
    +      "value" => "#{transaction.value.value}",
    +      "gas" => "#{transaction.gas}",
    +      "gasPrice" => "#{transaction.gas_price.value}",
    +      "isError" => if(transaction.status == :ok, do: "0", else: "1"),
    +      "txreceipt_status" => if(transaction.status == :ok, do: "1", else: "0"),
    +      "input" => "#{transaction.input}",
    +      "contractAddress" => "#{transaction.created_contract_address_hash}",
    +      "cumulativeGasUsed" => "#{transaction.cumulative_gas_used}",
    +      "gasUsed" => "#{transaction.gas_used}",
    +      "confirmations" => "#{transaction.confirmations}"
    +    }
    +  end
    +
    +  defp prepare_internal_transaction(internal_transaction) do
    +    %{
    +      "blockNumber" => "#{internal_transaction.block_number}",
    +      "timeStamp" => "#{DateTime.to_unix(internal_transaction.block_timestamp)}",
    +      "from" => "#{internal_transaction.from_address_hash}",
    +      "to" => "#{internal_transaction.to_address_hash}",
    +      "value" => "#{internal_transaction.value.value}",
    +      "contractAddress" => "#{internal_transaction.created_contract_address_hash}",
    +      "transactionHash" => to_string(internal_transaction.transaction_hash),
    +      "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"),
    +      "errCode" => "#{internal_transaction.error}"
    +    }
    +  end
    +
    +  defp prepare_common_token_transfer(token_transfer) do
    +    %{
    +      "blockNumber" => to_string(token_transfer.block_number),
    +      "timeStamp" => to_string(DateTime.to_unix(token_transfer.block_timestamp)),
    +      "hash" => to_string(token_transfer.transaction_hash),
    +      "nonce" => to_string(token_transfer.transaction_nonce),
    +      "blockHash" => to_string(token_transfer.block_hash),
    +      "from" => to_string(token_transfer.from_address_hash),
    +      "contractAddress" => to_string(token_transfer.token_contract_address_hash),
    +      "to" => to_string(token_transfer.to_address_hash),
    +      "logIndex" => to_string(token_transfer.token_log_index),
    +      "tokenName" => token_transfer.token_name,
    +      "tokenSymbol" => token_transfer.token_symbol,
    +      "tokenDecimal" => to_string(token_transfer.token_decimals),
    +      "transactionIndex" => to_string(token_transfer.transaction_index),
    +      "gas" => to_string(token_transfer.transaction_gas),
    +      "gasPrice" => to_string(token_transfer.transaction_gas_price.value),
    +      "gasUsed" => to_string(token_transfer.transaction_gas_used),
    +      "cumulativeGasUsed" => to_string(token_transfer.transaction_cumulative_gas_used),
    +      "input" => to_string(token_transfer.transaction_input),
    +      "confirmations" => to_string(token_transfer.confirmations)
    +    }
    +  end
    +
    +  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)
    +  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)
    +  end
    +
    +  defp prepare_token_transfer(%{token_type: "ERC-20"} = token_transfer) do
    +    token_transfer
    +    |> prepare_common_token_transfer()
    +    |> Map.put_new(:value, to_string(token_transfer.amount))
    +  end
    +
    +  defp prepare_token_transfer(token_transfer) do
    +    prepare_common_token_transfer(token_transfer)
    +  end
    +
    +  defp prepare_block(block) do
    +    %{
    +      "blockNumber" => to_string(block.number),
    +      "timeStamp" => to_string(block.timestamp)
    +    }
    +  end
    +
    +  defp prepare_token(token) do
    +    %{
    +      "balance" => to_string(token.balance),
    +      "contractAddress" => to_string(token.contract_address_hash),
    +      "name" => token.name,
    +      "decimals" => to_string(token.decimals),
    +      "symbol" => token.symbol,
    +      "type" => token.type
    +    }
    +    |> (&if(is_nil(token.id), do: &1, else: Map.put(&1, "id", token.id))).()
    +  end
    +
    +  defp balance(address) do
    +    address.fetched_coin_balance && address.fetched_coin_balance.value && "#{address.fetched_coin_balance.value}"
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex
    new file mode 100644
    index 0000000..70d5f0b
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex
    @@ -0,0 +1,44 @@
    +defmodule BlockScoutWeb.API.RPC.BlockView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.API.EthRPC.View, as: EthRPCView
    +  alias BlockScoutWeb.API.RPC.RPCView
    +  alias Explorer.Chain.{Hash, Wei}
    +  alias Explorer.EthRPC, as: EthRPC
    +
    +  def render("block_reward.json", %{block: block, reward: reward}) do
    +    reward_as_string =
    +      reward
    +      |> Wei.to(:wei)
    +      |> Decimal.to_string(:normal)
    +
    +    data = %{
    +      "blockNumber" => to_string(block.number),
    +      "timeStamp" => DateTime.to_unix(block.timestamp),
    +      "blockMiner" => Hash.to_string(block.miner_hash),
    +      "blockReward" => reward_as_string,
    +      "uncles" => nil,
    +      "uncleInclusionReward" => nil
    +    }
    +
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("getblocknobytime.json", %{block_number: block_number}) do
    +    data = %{
    +      "blockNumber" => to_string(block_number)
    +    }
    +
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("eth_block_number.json", %{number: number, id: id}) do
    +    result = EthRPC.encode_quantity(number)
    +
    +    EthRPCView.render("show.json", %{result: result, id: id})
    +  end
    +
    +  def render("error.json", %{error: error}) do
    +    RPCView.render("error.json", error: error)
    +  end
    +end
    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
    new file mode 100644
    index 0000000..ace6af7
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
    @@ -0,0 +1,221 @@
    +defmodule BlockScoutWeb.API.RPC.ContractView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.AddressView
    +  alias BlockScoutWeb.API.RPC.RPCView
    +  alias Ecto.Association.NotLoaded
    +  alias Explorer.Chain
    +  alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract}
    +
    +  defguardp is_empty_string(input) when input == "" or input == nil
    +
    +  def render("listcontracts.json", %{contracts: contracts}) do
    +    contracts = Enum.map(contracts, &prepare_contract/1)
    +
    +    RPCView.render("show.json", data: contracts)
    +  end
    +
    +  def render("getabi.json", %{abi: abi}) do
    +    RPCView.render("show.json", data: Jason.encode!(abi))
    +  end
    +
    +  def render("getsourcecode.json", %{contract: contract}) do
    +    RPCView.render("show.json", data: [prepare_source_code_contract(contract)])
    +  end
    +
    +  def render("error.json", assigns) do
    +    RPCView.render("error.json", assigns)
    +  end
    +
    +  def render("verify.json", %{contract: contract}) do
    +    RPCView.render("show.json", data: prepare_source_code_contract(contract))
    +  end
    +
    +  def render("show.json", %{result: result}) do
    +    RPCView.render("show.json", data: result)
    +  end
    +
    +  defp prepare_source_code_contract(address) do
    +    decompiled_smart_contract = latest_decompiled_smart_contract(address.decompiled_smart_contracts)
    +    contract = address.smart_contract || %{}
    +
    +    optimization = Map.get(contract, :optimization, "")
    +
    +    contract_output = %{
    +      "Address" => to_string(address.hash)
    +    }
    +
    +    contract_output
    +    |> set_decompiled_contract_data(decompiled_smart_contract)
    +    |> set_optimization_runs(contract, optimization)
    +    |> set_constructor_arguments(contract)
    +    |> set_external_libraries(contract)
    +    |> set_verified_contract_data(contract, address, optimization)
    +    |> set_proxy_info(contract)
    +  end
    +
    +  defp set_proxy_info(contract_output, contract) when contract == %{} do
    +    contract_output
    +  end
    +
    +  defp set_proxy_info(contract_output, contract) do
    +    result =
    +      if contract.is_proxy do
    +        contract_output
    +        |> Map.put_new(:ImplementationAddress, contract.implementation_address_hash_string)
    +      else
    +        contract_output
    +      end
    +
    +    is_proxy_string = if contract.is_proxy, do: "true", else: "false"
    +
    +    result
    +    |> Map.put_new(:IsProxy, is_proxy_string)
    +  end
    +
    +  defp set_decompiled_contract_data(contract_output, decompiled_smart_contract) do
    +    if decompiled_smart_contract do
    +      contract_output
    +      |> Map.put_new(:DecompiledSourceCode, decompiled_source_code(decompiled_smart_contract))
    +      |> Map.put_new(:DecompilerVersion, decompiler_version(decompiled_smart_contract))
    +    else
    +      contract_output
    +    end
    +  end
    +
    +  defp set_optimization_runs(contract_output, contract, optimization) do
    +    optimization_runs = Map.get(contract, :optimization_runs, "")
    +
    +    if optimization && optimization != "" do
    +      contract_output
    +      |> Map.put_new(:OptimizationRuns, optimization_runs)
    +    else
    +      contract_output
    +    end
    +  end
    +
    +  defp set_constructor_arguments(contract_output, %{constructor_arguments: arguments}) when is_empty_string(arguments),
    +    do: contract_output
    +
    +  defp set_constructor_arguments(contract_output, %{constructor_arguments: arguments}) do
    +    contract_output
    +    |> Map.put_new(:ConstructorArguments, arguments)
    +  end
    +
    +  defp set_constructor_arguments(contract_output, _), do: contract_output
    +
    +  defp set_external_libraries(contract_output, contract) do
    +    external_libraries = Map.get(contract, :external_libraries, [])
    +
    +    if Enum.count(external_libraries) > 0 do
    +      external_libraries_without_id =
    +        Enum.map(external_libraries, fn %{name: name, address_hash: address_hash} ->
    +          %{"name" => name, "address_hash" => address_hash}
    +        end)
    +
    +      contract_output
    +      |> Map.put_new(:ExternalLibraries, external_libraries_without_id)
    +    else
    +      contract_output
    +    end
    +  end
    +
    +  defp set_verified_contract_data(contract_output, contract, address, optimization) do
    +    contract_abi =
    +      if is_nil(address.smart_contract) do
    +        "Contract source code not verified"
    +      else
    +        Jason.encode!(contract.abi)
    +      end
    +
    +    contract_optimization =
    +      case optimization do
    +        true ->
    +          "true"
    +
    +        false ->
    +          "false"
    +
    +        "" ->
    +          ""
    +      end
    +
    +    if Map.equal?(contract, %{}) do
    +      contract_output
    +    else
    +      contract_output
    +      |> Map.put_new(:SourceCode, Map.get(contract, :contract_source_code, ""))
    +      |> Map.put_new(:ABI, contract_abi)
    +      |> Map.put_new(:ContractName, Map.get(contract, :name, ""))
    +      |> Map.put_new(:CompilerVersion, Map.get(contract, :compiler_version, ""))
    +      |> Map.put_new(:OptimizationUsed, contract_optimization)
    +      |> Map.put_new(:EVMVersion, Map.get(contract, :evm_version, ""))
    +      |> Map.put_new(:FileName, Map.get(contract, :file_path, "") || "")
    +      |> insert_additional_sources(address)
    +    end
    +  end
    +
    +  defp insert_additional_sources(output, address) do
    +    additional_sources_from_twin = Chain.get_address_verified_twin_contract(address.hash).additional_sources
    +
    +    additional_sources =
    +      if AddressView.smart_contract_verified?(address),
    +        do: address.smart_contract_additional_sources,
    +        else: additional_sources_from_twin
    +
    +    additional_sources_array =
    +      if additional_sources,
    +        do:
    +          Enum.map(additional_sources, fn src ->
    +            %{
    +              Filename: src.file_name,
    +              SourceCode: src.contract_source_code
    +            }
    +          end),
    +        else: []
    +
    +    if additional_sources_array == [],
    +      do: output,
    +      else: Map.put_new(output, :AdditionalSources, additional_sources_array)
    +  end
    +
    +  defp prepare_contract(%Address{
    +         hash: hash,
    +         smart_contract: nil
    +       }) do
    +    %{
    +      "Address" => to_string(hash),
    +      "ABI" => "Contract source code not verified"
    +    }
    +  end
    +
    +  defp prepare_contract(%Address{
    +         hash: hash,
    +         smart_contract: %SmartContract{} = contract
    +       }) do
    +    %{
    +      "Address" => to_string(hash),
    +      "ABI" => Jason.encode!(contract.abi),
    +      "ContractName" => contract.name,
    +      "CompilerVersion" => contract.compiler_version,
    +      "OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
    +    }
    +  end
    +
    +  defp latest_decompiled_smart_contract(%NotLoaded{}), do: nil
    +
    +  defp latest_decompiled_smart_contract([]), do: nil
    +
    +  defp latest_decompiled_smart_contract(contracts) do
    +    Enum.max_by(contracts, fn contract -> DateTime.to_unix(contract.inserted_at) end)
    +  end
    +
    +  defp decompiled_source_code(nil), do: "Contract source code not decompiled."
    +
    +  defp decompiled_source_code(%DecompiledSmartContract{decompiled_source_code: decompiled_source_code}) do
    +    decompiled_source_code
    +  end
    +
    +  defp decompiler_version(nil), do: ""
    +  defp decompiler_version(%DecompiledSmartContract{decompiler_version: decompiler_version}), do: decompiler_version
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex
    new file mode 100644
    index 0000000..6f2933d
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex
    @@ -0,0 +1,52 @@
    +defmodule BlockScoutWeb.API.RPC.LogsView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.API.RPC.RPCView
    +
    +  def render("getlogs.json", %{logs: logs}) do
    +    data = Enum.map(logs, &prepare_log/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("error.json", assigns) do
    +    RPCView.render("error.json", assigns)
    +  end
    +
    +  defp prepare_log(log) do
    +    %{
    +      "address" => "#{log.address_hash}",
    +      "topics" => get_topics(log),
    +      "data" => "#{log.data}",
    +      "blockNumber" => integer_to_hex(log.block_number),
    +      "timeStamp" => datetime_to_hex(log.block_timestamp),
    +      "gasPrice" => decimal_to_hex(log.gas_price.value),
    +      "gasUsed" => decimal_to_hex(log.gas_used),
    +      "logIndex" => integer_to_hex(log.index),
    +      "transactionHash" => "#{log.transaction_hash}",
    +      "transactionIndex" => integer_to_hex(log.transaction_index)
    +    }
    +  end
    +
    +  defp get_topics(%{
    +         first_topic: first_topic,
    +         second_topic: second_topic,
    +         third_topic: third_topic,
    +         fourth_topic: fourth_topic
    +       }) do
    +    [first_topic, second_topic, third_topic, fourth_topic]
    +  end
    +
    +  defp integer_to_hex(integer), do: "0x" <> String.downcase(Integer.to_string(integer, 16))
    +
    +  defp decimal_to_hex(decimal) do
    +    decimal
    +    |> Decimal.to_integer()
    +    |> integer_to_hex()
    +  end
    +
    +  defp datetime_to_hex(datetime) do
    +    datetime
    +    |> DateTime.to_unix()
    +    |> integer_to_hex()
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/rpc_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/rpc_view.ex
    new file mode 100644
    index 0000000..f877d94
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/rpc_view.ex
    @@ -0,0 +1,27 @@
    +defmodule BlockScoutWeb.API.RPC.RPCView do
    +  use BlockScoutWeb, :view
    +
    +  def render("show.json", %{data: data}) do
    +    %{
    +      "status" => "1",
    +      "message" => "OK",
    +      "result" => data
    +    }
    +  end
    +
    +  def render("show_value.json", %{data: data}) do
    +    {value, _} =
    +      data
    +      |> Float.parse()
    +
    +    value
    +  end
    +
    +  def render("error.json", %{error: message} = assigns) do
    +    %{
    +      "status" => "0",
    +      "message" => message,
    +      "result" => Map.get(assigns, :data)
    +    }
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex
    new file mode 100644
    index 0000000..91f6967
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex
    @@ -0,0 +1,53 @@
    +defmodule BlockScoutWeb.API.RPC.StatsView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.API.RPC.RPCView
    +
    +  def render("tokensupply.json", %{total_supply: token_supply}) do
    +    RPCView.render("show.json", data: token_supply)
    +  end
    +
    +  def render("ethsupplyexchange.json", %{total_supply: total_supply}) do
    +    RPCView.render("show.json", data: total_supply)
    +  end
    +
    +  def render("ethsupply.json", %{total_supply: total_supply}) do
    +    RPCView.render("show.json", data: total_supply)
    +  end
    +
    +  def render("coinsupply.json", %{total_supply: total_supply}) do
    +    RPCView.render("show_value.json", data: total_supply)
    +  end
    +
    +  def render("coinprice.json", %{rates: rates}) do
    +    RPCView.render("show.json", data: prepare_rates(rates))
    +  end
    +
    +  def render("totalfees.json", %{total_fees: total_fees}) do
    +    RPCView.render("show.json", data: total_fees)
    +  end
    +
    +  def render("error.json", assigns) do
    +    RPCView.render("error.json", assigns)
    +  end
    +
    +  defp prepare_rates(rates) do
    +    if rates do
    +      timestamp = rates.last_updated |> DateTime.to_unix() |> to_string()
    +
    +      %{
    +        "coin_btc" => to_string(rates.btc_value),
    +        "coin_btc_timestamp" => timestamp,
    +        "coin_usd" => to_string(rates.usd_value),
    +        "coin_usd_timestamp" => timestamp
    +      }
    +    else
    +      %{
    +        "coin_btc" => nil,
    +        "coin_btc_timestamp" => nil,
    +        "coin_usd" => nil,
    +        "coin_usd_timestamp" => nil
    +      }
    +    end
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex
    new file mode 100644
    index 0000000..9ccab7c
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex
    @@ -0,0 +1,37 @@
    +defmodule BlockScoutWeb.API.RPC.TokenView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.API.RPC.RPCView
    +
    +  def render("gettoken.json", %{token: token}) do
    +    RPCView.render("show.json", data: prepare_token(token))
    +  end
    +
    +  def render("gettokenholders.json", %{token_holders: token_holders}) do
    +    data = Enum.map(token_holders, &prepare_token_holder/1)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("error.json", assigns) do
    +    RPCView.render("error.json", assigns)
    +  end
    +
    +  defp prepare_token(token) do
    +    %{
    +      "type" => token.type,
    +      "name" => token.name,
    +      "symbol" => token.symbol,
    +      "totalSupply" => to_string(token.total_supply),
    +      "decimals" => to_string(token.decimals),
    +      "contractAddress" => to_string(token.contract_address_hash),
    +      "cataloged" => token.cataloged
    +    }
    +  end
    +
    +  defp prepare_token_holder(token_holder) do
    +    %{
    +      "address" => to_string(token_holder.address_hash),
    +      "value" => token_holder.value
    +    }
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
    new file mode 100644
    index 0000000..4b81b11
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
    @@ -0,0 +1,90 @@
    +defmodule BlockScoutWeb.API.RPC.TransactionView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.API.RPC.RPCView
    +
    +  def render("gettxinfo.json", %{
    +        transaction: transaction,
    +        block_height: block_height,
    +        logs: logs,
    +        next_page_params: next_page_params
    +      }) do
    +    data = prepare_transaction(transaction, block_height, logs, next_page_params)
    +    RPCView.render("show.json", data: data)
    +  end
    +
    +  def render("gettxreceiptstatus.json", %{status: status}) do
    +    prepared_status = prepare_tx_receipt_status(status)
    +    RPCView.render("show.json", data: %{"status" => prepared_status})
    +  end
    +
    +  def render("getstatus.json", %{error: error}) do
    +    RPCView.render("show.json", data: prepare_error(error))
    +  end
    +
    +  def render("error.json", assigns) do
    +    RPCView.render("error.json", assigns)
    +  end
    +
    +  defp prepare_tx_receipt_status(""), do: ""
    +
    +  defp prepare_tx_receipt_status(nil), do: ""
    +
    +  defp prepare_tx_receipt_status(:ok), do: "1"
    +
    +  defp prepare_tx_receipt_status(_), do: "0"
    +
    +  defp prepare_error("") do
    +    %{
    +      "isError" => "0",
    +      "errDescription" => ""
    +    }
    +  end
    +
    +  defp prepare_error(error) when is_binary(error) do
    +    %{
    +      "isError" => "1",
    +      "errDescription" => error
    +    }
    +  end
    +
    +  defp prepare_error(error) when is_atom(error) do
    +    %{
    +      "isError" => "1",
    +      "errDescription" => error |> Atom.to_string() |> String.replace("_", " ")
    +    }
    +  end
    +
    +  defp prepare_transaction(transaction, block_height, logs, next_page_params) do
    +    %{
    +      "hash" => "#{transaction.hash}",
    +      "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}",
    +      "blockNumber" => "#{transaction.block_number}",
    +      "confirmations" => "#{block_height - transaction.block_number}",
    +      "success" => if(transaction.status == :ok, do: true, else: false),
    +      "from" => "#{transaction.from_address_hash}",
    +      "to" => "#{transaction.to_address_hash}",
    +      "value" => "#{transaction.value.value}",
    +      "input" => "#{transaction.input}",
    +      "gasLimit" => "#{transaction.gas}",
    +      "gasUsed" => "#{transaction.gas_used}",
    +      "gasPrice" => "#{transaction.gas_price.value}",
    +      "logs" => Enum.map(logs, &prepare_log/1),
    +      "revertReason" => "#{transaction.revert_reason}",
    +      "next_page_params" => next_page_params
    +    }
    +  end
    +
    +  defp prepare_log(log) do
    +    %{
    +      "address" => "#{log.address_hash}",
    +      "topics" => get_topics(log),
    +      "data" => "#{log.data}",
    +      "index" => "#{log.index}"
    +    }
    +  end
    +
    +  defp get_topics(log) do
    +    [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic]
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v1/supply_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v1/supply_view.ex
    new file mode 100644
    index 0000000..df76632
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v1/supply_view.ex
    @@ -0,0 +1,10 @@
    +defmodule BlockScoutWeb.API.V1.SupplyView do
    +  use BlockScoutWeb, :view
    +
    +  def render("supply.json", %{total: total_supply, circulating: circulating_supply}) do
    +    %{
    +      "total_supply" => total_supply,
    +      "circulating_supply" => circulating_supply
    +    }
    +  end
    +end
    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 0000000..54511ab
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
    @@ -0,0 +1,59 @@
    +defmodule BlockScoutWeb.API.V2.AddressView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView}
    +  alias BlockScoutWeb.API.V2.Helper
    +
    +  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
    +    Helper.address_with_info(conn, address, address.hash)
    +  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 0000000..6bd12a4
    --- /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 0000000..1fde984
    --- /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 0000000..bddcddf
    --- /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 0000000..be0d9da
    --- /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 0000000..57f914b
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
    @@ -0,0 +1,108 @@
    +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(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" => to_string(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, address_hash) do
    +    %{"hash" => 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 0000000..434a654
    --- /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 0000000..e0bdae9
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex
    @@ -0,0 +1,13 @@
    +defmodule BlockScoutWeb.API.V2.TokenView do
    +  def render("token.json", %{token: token}) do
    +    %{
    +      "address" => token.contract_address_hash,
    +      "symbol" => token.symbol,
    +      "name" => token.name,
    +      "decimals" => token.decimals,
    +      "type" => token.type,
    +      "holders" => to_string(token.holder_count),
    +      "exchange_rate" => token.usd_value && to_string(token.usd_value)
    +    }
    +  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 0000000..dc1b9ef
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
    @@ -0,0 +1,447 @@
    +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, decoded: decoded}) do
    +    %{"raw" => raw, "decoded" => decoded}
    +  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)
    +    }
    +  end
    +
    +  def prepare_token_transfer_total(token_transfer) do
    +    case Helpers.token_transfer_amount_for_api(token_transfer) do
    +      {:ok, :erc721_instance} ->
    +        %{"token_id" => token_transfer.token_id}
    +
    +      {:ok, :erc1155_instance, value, decimals} ->
    +        %{"token_id" => token_transfer.token_id, "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" => transaction.block && transaction.block.timestamp,
    +      "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)
    +          utf8 = TransactionView.decoded_revert_reason(transaction)
    +          render(__MODULE__, "revert_reason.json", raw: hex, decoded: utf8)
    +      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
    +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
    new file mode 100644
    index 0000000..2026024
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
    @@ -0,0 +1,90 @@
    +defmodule BlockScoutWeb.APIDocsView do
    +  use BlockScoutWeb, :view
    +
    +  alias BlockScoutWeb.LayoutView
    +  alias Explorer
    +
    +  def action_tile_id(module, action) do
    +    "#{module}-#{action}"
    +  end
    +
    +  def query_params(module, action) do
    +    module_and_action(module, action) <> Enum.join(required_params(action))
    +  end
    +
    +  def input_placeholder(param) do
    +    "#{param.key} - #{param.description}"
    +  end
    +
    +  def model_type_definition(definition) when is_binary(definition) do
    +    definition
    +  end
    +
    +  def model_type_definition(definition_func) when is_function(definition_func, 1) do
    +    coin = Explorer.coin()
    +    definition_func.(coin)
    +  end
    +
    +  defp module_and_action(module, action) do
    +    "?module=#{module}&action=#{action.name}"
    +  end
    +
    +  defp required_params(action) do
    +    Enum.map(action.required_params, fn param ->
    +      "&#{param.key}=" <> "{#{param.placeholder}}"
    +    end)
    +  end
    +
    +  def blockscout_url(set_path) when set_path == false do
    +    url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
    +    host = url_params[:host]
    +
    +    scheme = Keyword.get(url_params, :scheme, "http")
    +
    +    if host != "localhost" do
    +      "#{scheme}://#{host}"
    +    else
    +      port = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:http][:port]
    +      "#{scheme}://#{host}:#{to_string(port)}"
    +    end
    +  end
    +
    +  def blockscout_url(set_path, is_api) when set_path == true do
    +    url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
    +    host = url_params[:host]
    +
    +    path =
    +      if is_api do
    +        url_params[:api_path]
    +      else
    +        url_params[:path]
    +      end
    +
    +    scheme = Keyword.get(url_params, :scheme, "http")
    +
    +    if host != "localhost" do
    +      "#{scheme}://#{host}#{path}"
    +    else
    +      port = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:http][:port]
    +      "#{scheme}://#{host}:#{to_string(port)}"
    +    end
    +  end
    +
    +  def api_url do
    +    is_api = true
    +    set_path = true
    +
    +    set_path
    +    |> blockscout_url(is_api)
    +    |> Path.join("api")
    +  end
    +
    +  def eth_rpc_api_url do
    +    is_api = true
    +    set_path = true
    +
    +    set_path
    +    |> blockscout_url(is_api)
    +    |> Path.join("api/eth-rpc")
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex
    new file mode 100644
    index 0000000..aef7f41
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex
    @@ -0,0 +1,17 @@
    +defmodule BlockScoutWeb.BlockTransactionView do
    +  use BlockScoutWeb, :view
    +
    +  import BlockScoutWeb.Gettext, only: [gettext: 1]
    +
    +  def block_not_found_message({:ok, true}) do
    +    gettext("Easy Cowboy! This block does not exist yet!")
    +  end
    +
    +  def block_not_found_message({:ok, false}) do
    +    gettext("This block has not been processed yet.")
    +  end
    +
    +  def block_not_found_message({:error, :hash}) do
    +    gettext("Block not found, please try again later.")
    +  end
    +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
    new file mode 100644
    index 0000000..7fdabe4
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex
    @@ -0,0 +1,85 @@
    +defmodule BlockScoutWeb.BlockView do
    +  use BlockScoutWeb, :view
    +
    +  import Math.Enum, only: [mean: 1]
    +
    +  alias Ecto.Association.NotLoaded
    +  alias Explorer.Chain
    +  alias Explorer.Chain.{Block, Wei}
    +  alias Explorer.Chain.Block.Reward
    +  alias Explorer.Counters.{BlockBurnedFeeCounter, BlockPriorityFeeCounter}
    +
    +  @dialyzer :no_match
    +
    +  def average_gas_price(%Block{transactions: transactions}) do
    +    average =
    +      transactions
    +      |> Enum.map(&Decimal.to_float(Wei.to(&1.gas_price, :gwei)))
    +      |> mean()
    +      |> Kernel.||(0)
    +      |> BlockScoutWeb.Cldr.Number.to_string!()
    +
    +    unit_text = gettext("Gwei")
    +
    +    "#{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"
    +
    +  @doc """
    +  Work-around for spec issue in `Cldr.Unit.to_string!/1`
    +  """
    +  def cldr_unit_to_string!(unit) do
    +    # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Unit.to_string! spec
    +    case :erlang.phash2(1, 1) do
    +      0 ->
    +        BlockScoutWeb.Cldr.Unit.to_string!(unit)
    +
    +      1 ->
    +        # does not occur
    +        ""
    +    end
    +  end
    +
    +  def formatted_gas(gas, format \\ []) do
    +    BlockScoutWeb.Cldr.Number.to_string!(gas, format)
    +  end
    +
    +  def formatted_timestamp(%Block{timestamp: timestamp}) do
    +    Timex.format!(timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime)
    +  end
    +
    +  def show_reward?([]), do: false
    +  def show_reward?(_), do: true
    +
    +  def block_reward_text(%Reward{address_hash: beneficiary_address, address_type: :validator}, block_miner_address) do
    +    if Application.get_env(:explorer, Explorer.Chain.Block.Reward, %{})[:keys_manager_contract_address] do
    +      %{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining(block_miner_address)
    +
    +      if beneficiary_address == block_miner_payout_address do
    +        gettext("Miner Reward")
    +      else
    +        gettext("Chore Reward")
    +      end
    +    else
    +      gettext("Miner Reward")
    +    end
    +  end
    +
    +  def block_reward_text(%Reward{address_type: :emission_funds}, _block_miner_address) do
    +    gettext("Emission Reward")
    +  end
    +
    +  def block_reward_text(%Reward{address_type: :uncle}, _block_miner_address) do
    +    gettext("Uncle Reward")
    +  end
    +
    +  def combined_rewards_value(block) do
    +    block
    +    |> Chain.block_combined_rewards()
    +    |> format_wei_value(:ether)
    +  end
    +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
    new file mode 100644
    index 0000000..43d66a2
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
    @@ -0,0 +1,72 @@
    +defmodule BlockScoutWeb.ChainView do
    +  use BlockScoutWeb, :view
    +
    +  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
    +
    +  def format_usd_value(nil), do: ""
    +
    +  def format_usd_value(value) do
    +    if Decimal.is_decimal(value) do
    +      "#{format_currency_value(Decimal.to_float(value))} USD"
    +    else
    +      "#{format_currency_value(value)} USD"
    +    end
    +  end
    +
    +  def format_currency_value(value, symbol \\ "$")
    +
    +  def format_currency_value(nil, _symbol), do: ""
    +
    +  def format_currency_value(%Decimal{} = value, symbol) do
    +    value
    +    |> Decimal.to_float()
    +    |> format_currency_value(symbol)
    +  end
    +
    +  def format_currency_value(value, _symbol) when not is_float(value) do
    +    "N/A"
    +  end
    +
    +  def format_currency_value(value, symbol) when is_float(value) and value < 0 do
    +    "#{symbol}0.00"
    +  end
    +
    +  def format_currency_value(value, symbol) when is_float(value) and value < 0.000001 do
    +    "Less than #{symbol}0.000001"
    +  end
    +
    +  def format_currency_value(value, symbol) when is_float(value) and value < 1 do
    +    "#{number_to_currency(value, unit: symbol, precision: 6)}"
    +  end
    +
    +  def format_currency_value(value, symbol) when is_float(value) and value < 100_000 do
    +    "#{number_to_currency(value, unit: symbol)}"
    +  end
    +
    +  def format_currency_value(value, _symbol) when value >= 1_000_000 and value <= 999_000_000 do
    +    {:ok, value} = Cldr.Number.to_string(value, format: :short, currency: :USD, fractional_digits: 2)
    +    value
    +  end
    +
    +  def format_currency_value(value, symbol) when is_float(value) do
    +    "#{number_to_currency(value, unit: symbol, precision: 0)}"
    +  end
    +
    +  defp gas_prices do
    +    case GasPriceOracle.get_gas_prices() do
    +      {:ok, gas_prices} ->
    +        gas_prices
    +
    +      nil ->
    +        nil
    +
    +      _ ->
    +        nil
    +    end
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex b/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex
    new file mode 100644
    index 0000000..e5b946e
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex
    @@ -0,0 +1,41 @@
    +defmodule BlockScoutWeb.CldrHelper.Number do
    +  @moduledoc """
    +  Work-arounds for `Cldr.Number` bugs
    +  """
    +
    +  def to_string(decimal, options) do
    +    # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Number.to_string spec
    +    case :erlang.phash2(1, 1) do
    +      0 ->
    +        BlockScoutWeb.Cldr.Number.to_string(decimal, options)
    +
    +      1 ->
    +        # does not occur
    +        ""
    +    end
    +  end
    +
    +  def to_string!(decimal) do
    +    # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Number.to_string! spec
    +    case :erlang.phash2(1, 1) do
    +      0 ->
    +        BlockScoutWeb.Cldr.Number.to_string!(decimal)
    +
    +      1 ->
    +        # does not occur
    +        ""
    +    end
    +  end
    +
    +  def to_string!(decimal, options) do
    +    # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Number.to_string! spec
    +    case :erlang.phash2(1, 1) do
    +      0 ->
    +        BlockScoutWeb.Cldr.Number.to_string!(decimal, options)
    +
    +      1 ->
    +        # does not occur
    +        ""
    +    end
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/common_components_view.ex b/apps/block_scout_web/lib/block_scout_web/views/common_components_view.ex
    new file mode 100644
    index 0000000..f950e79
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/common_components_view.ex
    @@ -0,0 +1,7 @@
    +defmodule BlockScoutWeb.CommonComponentsView do
    +  use BlockScoutWeb, :view
    +
    +  def balance_percentage_enabled?(total_supply) do
    +    Application.get_env(:block_scout_web, :show_percentage) && total_supply > 0
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
    new file mode 100644
    index 0000000..07b9564
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex
    @@ -0,0 +1,44 @@
    +defmodule BlockScoutWeb.CsvExportView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain
    +  alias Explorer.Chain.Address
    +
    +  defp type_display_name(type) do
    +    case type do
    +      "internal-transactions" -> "internal transactions"
    +      "transactions" -> "transactions"
    +      "token-transfers" -> "token transfers"
    +      "logs" -> "logs"
    +      _ -> ""
    +    end
    +  end
    +
    +  defp type_download_path(type) do
    +    case type do
    +      "internal-transactions" -> :internal_transactions_csv
    +      "transactions" -> :transactions_csv
    +      "token-transfers" -> :token_transfers_csv
    +      "logs" -> :logs_csv
    +      _ -> ""
    +    end
    +  end
    +
    +  defp address_checksum(address_hash_string) do
    +    with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do
    +      address_hash
    +      |> Address.checksum()
    +    end
    +  end
    +
    +  defp default_period_start do
    +    DateTime.utc_now()
    +    |> Timex.shift(months: -1)
    +    |> Timex.format!("{YYYY}-{0M}-{0D}")
    +  end
    +
    +  defp default_period_end do
    +    DateTime.utc_now()
    +    |> Timex.format!("{YYYY}-{0M}-{0D}")
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex
    new file mode 100644
    index 0000000..cd11a42
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex
    @@ -0,0 +1,95 @@
    +defmodule BlockScoutWeb.CurrencyHelpers do
    +  @moduledoc """
    +  Helper functions for interacting with `t:BlockScoutWeb.ExchangeRates.USD.t/0` values.
    +  """
    +
    +  alias BlockScoutWeb.CldrHelper.Number
    +
    +  @doc """
    +  Formats the given integer value to a currency format.
    +
    +  ## Examples
    +
    +      iex> BlockScoutWeb.CurrencyHelpers.format_integer_to_currency(1000000)
    +      "1,000,000"
    +  """
    +  @spec format_integer_to_currency(non_neg_integer()) :: String.t()
    +  def format_integer_to_currency(value) do
    +    {:ok, formatted} = Number.to_string(value, format: "#,##0")
    +
    +    formatted
    +  end
    +
    +  @doc """
    +  Formats the given amount according to given decimals.
    +
    +  ## Examples
    +
    +      iex> format_according_to_decimals(nil, Decimal.new(5))
    +      "-"
    +
    +      iex> format_according_to_decimals(Decimal.new(20500000), Decimal.new(5))
    +      "205"
    +
    +      iex> format_according_to_decimals(Decimal.new(20500000), Decimal.new(7))
    +      "2.05"
    +
    +      iex> format_according_to_decimals(Decimal.new(205000), Decimal.new(12))
    +      "0.000000205"
    +
    +      iex> format_according_to_decimals(Decimal.new(205000), Decimal.new(2))
    +      "2,050"
    +
    +      iex> format_according_to_decimals(205000, Decimal.new(2))
    +      "2,050"
    +
    +      iex> format_according_to_decimals(105000, Decimal.new(0))
    +      "105,000"
    +
    +      iex> format_according_to_decimals(105000000000000000000, Decimal.new(100500))
    +      "105"
    +
    +      iex> format_according_to_decimals(105000000000000000000, nil)
    +      "105,000,000,000,000,000,000"
    +  """
    +  @spec format_according_to_decimals(non_neg_integer() | nil, nil) :: String.t()
    +  def format_according_to_decimals(nil, _) do
    +    "-"
    +  end
    +
    +  def format_according_to_decimals(value, nil) do
    +    format_according_to_decimals(value, Decimal.new(0))
    +  end
    +
    +  def format_according_to_decimals(value, decimals) when is_integer(value) do
    +    value
    +    |> Decimal.new()
    +    |> format_according_to_decimals(decimals)
    +  end
    +
    +  @spec format_according_to_decimals(Decimal.t(), Decimal.t()) :: String.t()
    +  def format_according_to_decimals(value, decimals) do
    +    if Decimal.compare(decimals, 24) == :gt do
    +      format_according_to_decimals(value, Decimal.new(18))
    +    else
    +      value
    +      |> divide_decimals(decimals)
    +      |> thousands_separator()
    +    end
    +  end
    +
    +  defp thousands_separator(value) do
    +    if Decimal.to_float(value) > 999 do
    +      Number.to_string!(value)
    +    else
    +      Decimal.to_string(value, :normal)
    +    end
    +  end
    +
    +  @spec divide_decimals(Decimal.t(), Decimal.t()) :: Decimal.t()
    +  def divide_decimals(%{sign: sign, coef: coef, exp: exp}, decimals) do
    +    sign
    +    |> Decimal.new(coef, exp - Decimal.to_integer(decimals))
    +    |> Decimal.normalize()
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/error_422.ex b/apps/block_scout_web/lib/block_scout_web/views/error_422.ex
    new file mode 100644
    index 0000000..b813dc9
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/error_422.ex
    @@ -0,0 +1,5 @@
    +defmodule BlockScoutWeb.Error422View do
    +  use BlockScoutWeb, :view
    +
    +  @dialyzer :no_match
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/error_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/error_helpers.ex
    new file mode 100644
    index 0000000..ff3e632
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/error_helpers.ex
    @@ -0,0 +1,59 @@
    +defmodule BlockScoutWeb.ErrorHelpers do
    +  @moduledoc """
    +  Conveniences for translating and building error messages.
    +  """
    +
    +  use Phoenix.HTML
    +
    +  alias Ecto.Changeset
    +  alias Phoenix.HTML.Form
    +  alias Plug.Conn
    +
    +  @doc """
    +  Generates tag for inlined form input errors.
    +  """
    +  def error_tag(form, field, opts \\ []) do
    +    Enum.map(Keyword.get_values(form.errors, field), fn error ->
    +      content_tag(:span, translate_error(error), Keyword.merge([class: "has-error"], opts))
    +    end)
    +  end
    +
    +  @doc """
    +  Gets the errors for a form's input.
    +  """
    +  def errors_for_field(%Form{source: %Conn{}}, _), do: []
    +
    +  def errors_for_field(%Form{source: %Changeset{action: nil}}, _), do: []
    +
    +  def errors_for_field(%Form{source: %Changeset{action: :ignore}}, _), do: []
    +
    +  def errors_for_field(%Form{source: %Changeset{errors: errors}}, field) do
    +    for error <- Keyword.get_values(errors, field) do
    +      translate_error(error)
    +    end
    +  end
    +
    +  @doc """
    +  Translates an error message using gettext.
    +  """
    +  def translate_error({msg, opts}) do
    +    # Because error messages were defined within Ecto, we must
    +    # call the Gettext module passing our Gettext backend. We
    +    # also use the "errors" domain as translations are placed
    +    # in the errors.po file.
    +    # Ecto will pass the :count keyword if the error message is
    +    # meant to be pluralized.
    +    # On your own code and templates, depending on whether you
    +    # need the message to be pluralized or not, this could be
    +    # written simply as:
    +    #
    +    #     dngettext "errors", "1 file", "%{count} files", count
    +    #     dgettext "errors", "is invalid"
    +    #
    +    if count = opts[:count] do
    +      Gettext.dngettext(BlockScoutWeb.Gettext, "errors", msg, msg, count, opts)
    +    else
    +      Gettext.dgettext(BlockScoutWeb.Gettext, "errors", msg, opts)
    +    end
    +  end
    +end
    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
    new file mode 100644
    index 0000000..090159d
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/error_view.ex
    @@ -0,0 +1,34 @@
    +defmodule BlockScoutWeb.ErrorView do
    +  use BlockScoutWeb, :view
    +
    +  # when type in ["json", "html"]
    +  def render("404." <> _type, _assigns) do
    +    "Page not found"
    +  end
    +
    +  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." <> _type, _assigns) do
    +    "Internal server error"
    +  end
    +
    +  # In case no render clause matches or no
    +  # template is found, let's render it as 500
    +  def template_not_found(_template, assigns) do
    +    render("500.html", assigns)
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/form_view.ex b/apps/block_scout_web/lib/block_scout_web/views/form_view.ex
    new file mode 100644
    index 0000000..06144b1
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/form_view.ex
    @@ -0,0 +1,66 @@
    +defmodule BlockScoutWeb.FormView do
    +  use BlockScoutWeb, :view
    +
    +  alias Phoenix.HTML.Form
    +
    +  @type text_input_type :: :email | :hidden | :password | :text
    +  @type text_field_option ::
    +          {:default_value, String.t()}
    +          | {:id, String.t()}
    +          | {:label, String.t()}
    +          | {:placeholder, String.t()}
    +          | {:required, boolean()}
    +  defguard is_text_input(type) when type in ~w(email hidden password text)a
    +
    +  @doc """
    +  Renders a text input field with certain properties.
    +
    +  ## Supported Options
    +
    +  * `:label` - Label for the input field
    +
    +  ## Options as HTML 5 Attriutes
    +
    +  The following options will be applied as HTML 5 attributes on the
    +  `` element:
    +
    +  * `:default_value` - Default value to attach to the input field
    +  * `:id` - ID to attatch to the input field
    +  * `:placeholder` - Placeholder text for the input field
    +  * `:required` - Mark the input field as required
    +  * `:type` - Input field type
    +  """
    +  @spec text_field(Form.t(), atom(), text_input_type(), [text_field_option()]) :: Phoenix.HTML.safe()
    +  def text_field(%Form{} = form, form_key, input_type, opts \\ [])
    +      when is_text_input(input_type) and is_atom(form_key) do
    +    errors = errors_for_field(form, form_key)
    +    label = Keyword.get(opts, :label)
    +    id = Keyword.get(opts, :id)
    +
    +    supported_input_field_attrs = ~w(default_value id placeholder required)a
    +    base_input_field_opts = Keyword.take(opts, supported_input_field_attrs)
    +
    +    input_field_class =
    +      case errors do
    +        [_ | _] -> "form-control is-invalid"
    +        _ -> "form-control"
    +      end
    +
    +    input_field_opts = Keyword.put(base_input_field_opts, :class, input_field_class)
    +    input_field = input_for_type(input_type).(form, form_key, input_field_opts)
    +
    +    render_opts = [
    +      errors: errors,
    +      id: id,
    +      input_field: input_field,
    +      label: label
    +    ]
    +
    +    render("text_field.html", render_opts)
    +  end
    +
    +  defp input_for_type(:email), do: &email_input/3
    +  defp input_for_type(:text), do: &text_input/3
    +  defp input_for_type(:hidden), do: &hidden_input/3
    +  defp input_for_type(:password), do: &password_input/3
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/icons_view.ex b/apps/block_scout_web/lib/block_scout_web/views/icons_view.ex
    new file mode 100644
    index 0000000..40c5ee3
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/icons_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.IconsView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex
    new file mode 100644
    index 0000000..46252f7
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex
    @@ -0,0 +1,29 @@
    +defmodule BlockScoutWeb.InternalTransactionView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain.InternalTransaction
    +
    +  import BlockScoutWeb.Gettext
    +
    +  @doc """
    +  Returns the formatted string for the type of the internal transaction.
    +
    +  When the type is `call`, we return the formatted string for the call type.
    +
    +  Examples:
    +
    +  iex> BlockScoutWeb.InternalTransactionView.type(%Explorer.Chain.InternalTransaction{type: :reward})
    +  "Reward"
    +
    +  iex> BlockScoutWeb.InternalTransactionView.type(%Explorer.Chain.InternalTransaction{type: :call, call_type: :delegatecall})
    +  "Delegate Call"
    +  """
    +  def type(%InternalTransaction{type: :call, call_type: :call}), do: gettext("Call")
    +  def type(%InternalTransaction{type: :call, call_type: :callcode}), do: gettext("Call Code")
    +  def type(%InternalTransaction{type: :call, call_type: :delegatecall}), do: gettext("Delegate Call")
    +  def type(%InternalTransaction{type: :call, call_type: :staticcall}), do: gettext("Static Call")
    +  def type(%InternalTransaction{type: :create}), do: gettext("Create")
    +  def type(%InternalTransaction{type: :create2}), do: gettext("Create2")
    +  def type(%InternalTransaction{type: :selfdestruct}), do: gettext("Self-Destruct")
    +  def type(%InternalTransaction{type: :reward}), do: gettext("Reward")
    +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
    new file mode 100644
    index 0000000..e94cbc0
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
    @@ -0,0 +1,281 @@
    +defmodule BlockScoutWeb.LayoutView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.{Chain, CustomContractsHelpers}
    +  alias Plug.Conn
    +  alias Poison.Parser
    +
    +  import BlockScoutWeb.AddressView, only: [from_address_hash: 1]
    +
    +  @default_other_networks [
    +    %{
    +      title: "POA",
    +      url: "https://blockscout.com/poa/core"
    +    },
    +    %{
    +      title: "Sokol",
    +      url: "https://blockscout.com/poa/sokol",
    +      test_net?: true
    +    },
    +    %{
    +      title: "Gnosis Chain",
    +      url: "https://blockscout.com/xdai/mainnet"
    +    },
    +    %{
    +      title: "Ethereum Classic",
    +      url: "https://blockscout.com/etc/mainnet",
    +      other?: true
    +    },
    +    %{
    +      title: "RSK",
    +      url: "https://blockscout.com/rsk/mainnet",
    +      other?: true
    +    }
    +  ]
    +
    +  alias BlockScoutWeb.SocialMedia
    +
    +  def logo do
    +    Keyword.get(application_config(), :logo)
    +  end
    +
    +  def logo_footer do
    +    Keyword.get(application_config(), :logo_footer) || Keyword.get(application_config(), :logo)
    +  end
    +
    +  def logo_text do
    +    Keyword.get(application_config(), :logo_text) || nil
    +  end
    +
    +  def subnetwork_title do
    +    Keyword.get(application_config(), :subnetwork) || "Sokol"
    +  end
    +
    +  def network_title do
    +    Keyword.get(application_config(), :network) || "POA"
    +  end
    +
    +  defp application_config do
    +    Application.get_env(:block_scout_web, BlockScoutWeb.Chain)
    +  end
    +
    +  def configured_social_media_services do
    +    SocialMedia.links()
    +  end
    +
    +  def issue_link(conn) do
    +    params = [
    +      labels: "BlockScout",
    +      body: issue_body(conn),
    +      title: subnetwork_title() <> ": "
    +    ]
    +
    +    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
    +    user_agent =
    +      case Conn.get_req_header(conn, "user-agent") do
    +        [] -> "unknown"
    +        [user_agent] -> if String.valid?(user_agent), do: user_agent, else: "unknown"
    +        _other -> "unknown"
    +      end
    +
    +    """
    +    *Describe your issue here.*
    +
    +    ### Environment
    +    * Elixir Version: #{System.version()}
    +    * Erlang Version: #{System.otp_release()}
    +    * BlockScout Version: #{version()}
    +
    +    * User Agent: `#{user_agent}`
    +
    +    ### Steps to reproduce
    +
    +    *Tell us how to reproduce this issue. If possible, push up a branch to your fork with a regression test we can run to reproduce locally.*
    +
    +    ### Expected Behaviour
    +
    +    *Tell us what should happen.*
    +
    +    ### Actual Behaviour
    +
    +    *Tell us what happens instead.*
    +    """
    +  end
    +
    +  def version do
    +    BlockScoutWeb.version()
    +  end
    +
    +  def release_link(version) do
    +    release_link_env_var = Application.get_env(:block_scout_web, :release_link)
    +
    +    release_link =
    +      cond do
    +        version == "" || version == nil ->
    +          nil
    +
    +        release_link_env_var == "" || release_link_env_var == nil ->
    +          "https://github.com/blockscout/blockscout/releases/tag/" <> version
    +
    +        true ->
    +          release_link_env_var
    +      end
    +
    +    if release_link == nil do
    +      ""
    +    else
    +      html_escape({:safe, "#{version}"})
    +    end
    +  end
    +
    +  def ignore_version?("unknown"), do: true
    +  def ignore_version?(_), do: false
    +
    +  def other_networks do
    +    get_other_networks =
    +      if Application.get_env(:block_scout_web, :other_networks) do
    +        try do
    +          :block_scout_web
    +          |> Application.get_env(:other_networks)
    +          |> Parser.parse!(%{keys: :atoms!})
    +        rescue
    +          _ ->
    +            []
    +        end
    +      else
    +        @default_other_networks
    +      end
    +
    +    get_other_networks
    +    |> Enum.reject(fn %{title: title} ->
    +      title == subnetwork_title()
    +    end)
    +    |> Enum.sort()
    +  end
    +
    +  def main_nets(nets) do
    +    nets
    +    |> Enum.reject(&Map.get(&1, :test_net?))
    +  end
    +
    +  def test_nets(nets) do
    +    nets
    +    |> Enum.filter(&Map.get(&1, :test_net?))
    +  end
    +
    +  def dropdown_nets do
    +    other_networks()
    +    |> Enum.reject(&Map.get(&1, :hide_in_dropdown?))
    +  end
    +
    +  def dropdown_main_nets do
    +    dropdown_nets()
    +    |> main_nets()
    +  end
    +
    +  def dropdown_test_nets do
    +    dropdown_nets()
    +    |> test_nets()
    +  end
    +
    +  def dropdown_head_main_nets do
    +    dropdown_nets()
    +    |> main_nets()
    +    |> Enum.reject(&Map.get(&1, :other?))
    +  end
    +
    +  def dropdown_other_nets do
    +    dropdown_nets()
    +    |> main_nets()
    +    |> Enum.filter(&Map.get(&1, :other?))
    +  end
    +
    +  def other_explorers do
    +    if Application.get_env(:block_scout_web, :link_to_other_explorers) do
    +      decode_other_explorers_json(Application.get_env(:block_scout_web, :other_explorers, []))
    +    else
    +      []
    +    end
    +  end
    +
    +  defp decode_other_explorers_json(data) do
    +    Jason.decode!(~s(#{data}))
    +  rescue
    +    _ -> []
    +  end
    +
    +  def webapp_url(conn) do
    +    :block_scout_web
    +    |> Application.get_env(:webapp_url)
    +    |> validate_url()
    +    |> case do
    +      :error -> chain_path(conn, :show)
    +      {:ok, url} -> url
    +    end
    +  end
    +
    +  def api_url do
    +    :block_scout_web
    +    |> Application.get_env(:api_url)
    +    |> validate_url()
    +    |> case do
    +      :error -> ""
    +      {:ok, url} -> url
    +    end
    +  end
    +
    +  def apps_list do
    +    apps = Application.get_env(:block_scout_web, :apps)
    +
    +    if apps do
    +      try do
    +        apps
    +        |> Parser.parse!(%{keys: :atoms!})
    +      rescue
    +        _ ->
    +          []
    +      end
    +    else
    +      []
    +    end
    +  end
    +
    +  defp validate_url(url) when is_binary(url) do
    +    case URI.parse(url) do
    +      %URI{host: nil} -> :error
    +      _ -> {:ok, url}
    +    end
    +  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/log_view.ex b/apps/block_scout_web/lib/block_scout_web/views/log_view.ex
    new file mode 100644
    index 0000000..fe12ba9
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/log_view.ex
    @@ -0,0 +1,3 @@
    +defmodule BlockScoutWeb.LogView do
    +  use BlockScoutWeb, :view
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/page_not_found.ex b/apps/block_scout_web/lib/block_scout_web/views/page_not_found.ex
    new file mode 100644
    index 0000000..b5a18f0
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/page_not_found.ex
    @@ -0,0 +1,5 @@
    +defmodule BlockScoutWeb.PageNotFoundView do
    +  use BlockScoutWeb, :view
    +
    +  @dialyzer :no_match
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/pending_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/pending_transaction_view.ex
    new file mode 100644
    index 0000000..12ba2fe
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/pending_transaction_view.ex
    @@ -0,0 +1,5 @@
    +defmodule BlockScoutWeb.PendingTransactionView do
    +  use BlockScoutWeb, :view
    +
    +  @dialyzer :no_match
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/render_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/render_helpers.ex
    new file mode 100644
    index 0000000..20c35fa
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/render_helpers.ex
    @@ -0,0 +1,21 @@
    +defmodule BlockScoutWeb.RenderHelpers do
    +  @moduledoc """
    +  Helper functions to render partials from view modules
    +  """
    +  use BlockScoutWeb, :view
    +
    +  @doc """
    +  Renders html using:
    +  * A list of args including `:view_module` and `:partial` to render a partial with the required keyword list.
    +  * Text that will pass directly through to the template
    +  """
    +  def render_partial(args) when is_list(args) do
    +    render(
    +      Keyword.get(args, :view_module),
    +      Keyword.get(args, :partial),
    +      args
    +    )
    +  end
    +
    +  def render_partial(text), do: text
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/script_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/script_helpers.ex
    new file mode 100644
    index 0000000..730365c
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/script_helpers.ex
    @@ -0,0 +1,28 @@
    +defmodule BlockScoutWeb.Views.ScriptHelpers do
    +  @moduledoc """
    +  Helpers for rendering view specific script tags.
    +  """
    +
    +  import Phoenix.HTML, only: [sigil_E: 2]
    +  import BlockScoutWeb.Router.Helpers, only: [static_path: 2]
    +
    +  def render_scripts(conn, file_names) do
    +    conn
    +    |> files(file_names)
    +    |> Enum.map(fn file ->
    +      ~E"""
    +        
    +      """
    +    end)
    +  end
    +
    +  defp files(conn, file_names) do
    +    file_names
    +    |> List.wrap()
    +    |> Enum.map(fn file ->
    +      path = "/" <> Path.join("js", file)
    +
    +      static_path(conn, path)
    +    end)
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/search_view.ex
    new file mode 100644
    index 0000000..51bf1b8
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/search_view.ex
    @@ -0,0 +1,19 @@
    +defmodule BlockScoutWeb.SearchView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain
    +  alias Floki
    +
    +  def highlight_search_result(result, query) do
    +    re = ~r/#{query}/i
    +
    +    safe_result =
    +      result
    +      |> html_escape()
    +      |> safe_to_string()
    +
    +    re
    +    |> Regex.replace(safe_result, "\\g{0}", global: true)
    +    |> raw()
    +  end
    +end
    diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
    new file mode 100644
    index 0000000..e5bbb11
    --- /dev/null
    +++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
    @@ -0,0 +1,288 @@
    +defmodule BlockScoutWeb.SmartContractView do
    +  use BlockScoutWeb, :view
    +
    +  alias Explorer.Chain
    +  alias Explorer.Chain.{Address, Transaction}
    +  alias Explorer.Chain.Hash.Address, as: HashAddress
    +  alias Explorer.SmartContract.Helper
    +
    +  def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
    +
    +  def queryable?(inputs) when is_nil(inputs), do: false
    +
    +  def writable?(function) when not is_nil(function),
    +    do:
    +      !Helper.constructor?(function) && !Helper.event?(function) &&
    +        (Helper.payable?(function) || Helper.nonpayable?(function))
    +
    +  def writable?(function) when is_nil(function), do: false
    +
    +  def outputs?(outputs) when not is_nil(outputs) do
    +    case outputs do
    +      {:error, _} -> false
    +      _ -> Enum.any?(outputs)
    +    end
    +  end
    +
    +  def outputs?(outputs) when is_nil(outputs), do: false
    +
    +  def error?(outputs) when not is_nil(outputs) do
    +    case outputs do
    +      {:error, _} -> true
    +      _ -> false
    +    end
    +  end
    +
    +  def error?(outputs) when is_nil(outputs), do: false
    +
    +  def address?(type), do: type in ["address", "address payable"]
    +  def int?(type), do: String.contains?(type, "int") && !String.contains?(type, "[")
    +
    +  def named_argument?(%{"name" => ""}), do: false
    +  def named_argument?(%{"name" => nil}), do: false
    +  def named_argument?(%{"name" => _}), do: true
    +  def named_argument?(_), do: false
    +
    +  def values_with_type(value, type, names, index, components \\ nil)
    +
    +  def values_with_type(value, type, names, index, components) when is_list(value) do
    +    cond do
    +      String.starts_with?(type, "tuple") ->
    +        tuple_types =
    +          type
    +          |> String.slice(0..-3)
    +          |> supplement_type_with_components(components)
    +
    +        values =
    +          value
    +          |> tuple_array_to_array(tuple_types, fetch_name(names, index + 1))
    +          |> Enum.join("),\n(")
    +
    +        render_array_type_value(type, "(\n" <> values <> ")", fetch_name(names, index))
    +
    +      String.starts_with?(type, "address") ->
    +        values =
    +          value
    +          |> Enum.map_join(", ", &binary_to_utf_string(&1))
    +
    +        render_array_type_value(type, values, fetch_name(names, index))
    +
    +      String.starts_with?(type, "bytes") ->
    +        values =
    +          value
    +          |> Enum.map_join(", ", &binary_to_utf_string(&1))
    +
    +        render_array_type_value(type, values, fetch_name(names, index))
    +
    +      true ->
    +        values =
    +          value
    +          |> Enum.join("),\n(")
    +
    +        render_array_type_value(type, "(\n" <> values <> ")", fetch_name(names, index))
    +    end
    +  end
    +
    +  def values_with_type(value, type, names, index, _components) when is_tuple(value) do
    +    values =
    +      value
    +      |> tuple_to_array(type, fetch_name(names, index + 1))
    +      |> Enum.join("")
    +
    +    render_type_value(type, values, fetch_name(names, index))
    +  end
    +
    +  def values_with_type(value, type, names, index, _components) when type in [:address, "address", "address payable"] do
    +    case HashAddress.cast(value) do
    +      {:ok, address} ->
    +        render_type_value("address", to_string(address), fetch_name(names, index))
    +
    +      _ ->
    +        ""
    +    end
    +  end
    +
    +  def values_with_type(value, "string", names, index, _components),
    +    do: render_type_value("string", value |> Helper.sanitize_input(), fetch_name(names, index))
    +
    +  def values_with_type(value, :string, names, index, _components),
    +    do: render_type_value("string", value |> Helper.sanitize_input(), fetch_name(names, index))
    +
    +  def values_with_type(value, :bytes, names, index, _components),
    +    do: render_type_value("bytes", value |> Helper.sanitize_input(), fetch_name(names, index))
    +
    +  def values_with_type(value, "bool", names, index, _components),
    +    do: render_type_value("bool", to_string(value), fetch_name(names, index))
    +
    +  def values_with_type(value, :bool, names, index, _components),
    +    do: render_type_value("bool", to_string(value), fetch_name(names, index))
    +
    +  def values_with_type(value, type, names, index, _components),
    +    do: render_type_value(type, binary_to_utf_string(value), fetch_name(names, index))
    +
    +  def values_with_type(value, :error, _components), do: render_type_value("error", value, "error")
    +
    +  defp fetch_name(nil, _index), do: nil
    +
    +  defp fetch_name([], _index), do: nil
    +
    +  defp fetch_name(names, index) when is_list(names) do
    +    Enum.at(names, index)
    +  end
    +
    +  defp fetch_name(name, _index) when is_binary(name) do
    +    name
    +  end
    +
    +  def wrap_output(value, is_too_long \\ false) do
    +    if is_too_long do
    +      "
    Click to view#{value}
    " + else + "#{value}" + end + end + + defp tuple_array_to_array(value, type, names) do + value + |> Enum.map(fn item -> + tuple_to_array(item, type, names) + end) + end + + defp tuple_to_array(value, type, names) do + types_string = + type + |> String.slice(6..-2) + + types = + if String.trim(types_string) == "" do + [] + else + types_string + |> String.split(",") + end + + {tuple_types, _} = + types + |> Enum.reduce({[], nil}, fn val, acc -> + {arr, to_merge} = acc + + if to_merge do + if count_string_symbols(val)["]"] > count_string_symbols(val)["["] do + updated_arr = update_last_list_item(arr, val) + {updated_arr, !to_merge} + else + updated_arr = update_last_list_item(arr, val) + {updated_arr, to_merge} + end + else + if count_string_symbols(val)["["] > count_string_symbols(val)["]"] do + # credo:disable-for-next-line + {arr ++ [val], !to_merge} + else + # credo:disable-for-next-line + {arr ++ [val], to_merge} + end + end + end) + + values_list = + value + |> Tuple.to_list() + + values_types_list = Enum.zip(tuple_types, values_list) + + values_types_list + |> Enum.with_index() + |> Enum.map(fn {{type, value}, index} -> + values_with_type(value, type, fetch_name(names, index), 0) + end) + end + + defp update_last_list_item(arr, new_val) do + arr + |> Enum.with_index() + |> Enum.map(fn {item, index} -> + if index == Enum.count(arr) - 1 do + item <> "," <> new_val + else + item + end + end) + end + + defp count_string_symbols(str) do + str + |> String.graphemes() + |> Enum.reduce(%{"[" => 0, "]" => 0}, fn char, acc -> + Map.update(acc, char, 1, &(&1 + 1)) + end) + end + + defp binary_to_utf_string(item) do + case Integer.parse(to_string(item)) do + {item_integer, ""} -> + to_string(item_integer) + + _ -> + if is_binary(item) do + if String.starts_with?(item, "0x") do + item + else + "0x" <> Base.encode16(item, case: :lower) + end + else + to_string(item) + end + end + end + + defp render_type_value(type, value, type) do + "
    (#{type}) : #{value}
    " + end + + defp render_type_value(type, value, name) do + "
    #{name} (#{type}) : #{value}
    " + end + + defp render_array_type_value(type, values, name) do + value_to_display = "[" <> values <> "]" + + render_type_value(type, value_to_display, name) + end + + defp supplement_type_with_components(type, components) do + if type == "tuple" && components do + types = + components + |> Enum.map_join(",", fn component -> + Map.get(component, "type") + end) + + "tuple[" <> types <> "]" + else + type + end + end + + def decode_revert_reason(to_address, revert_reason) do + smart_contract = Chain.address_hash_to_smart_contract(to_address) + + Transaction.decoded_revert_reason( + %Transaction{to_address: %{smart_contract: smart_contract}, hash: to_address}, + revert_reason + ) + end + + def decode_hex_revert_reason(hex_revert_reason) do + case Integer.parse(hex_revert_reason, 16) do + {number, ""} -> + :binary.encode_unsigned(number) + + _ -> + hex_revert_reason + end + end + + def not_last_element?(length, index), do: length > 1 and index < length - 1 +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tab_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/tab_helpers.ex new file mode 100644 index 0000000..f07e992 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tab_helpers.ex @@ -0,0 +1,66 @@ +defmodule BlockScoutWeb.TabHelpers do + @moduledoc """ + Helper functions for dealing with tabs, which are very common between pages. + """ + + @doc """ + Get the current status of a tab by its name and the request path. + + A tab is considered active if its name responds true to active?/2. + + * returns the string "active" if the tab active. + * returns nil if the tab is not active. + + ## Examples + + iex> BlockScoutWeb.TabHelpers.tab_status("token", "/page/0xSom3tH1ng/token") + "active" + + iex> BlockScoutWeb.TabHelpers.tab_status("token", "/page/0xSom3tH1ng/token_transfer") + nil + """ + def tab_status(tab_name, request_path, show_token_transfers \\ false) do + if tab_active?(tab_name, request_path) do + "active" + else + case request_path do + "/tx/" <> "0x" <> <<_tx_hash::binary-size(64)>> -> + cond do + tab_name == "token-transfers" && show_token_transfers -> + "active" + + tab_name == "internal-transactions" && !show_token_transfers -> + "active" + + true -> + nil + end + + _ -> + nil + end + end + end + + @doc """ + Check if the given tab is the current tab given the request path. + + It is considered active if there is a substring that exactly matches the tab name in the path. + + * returns true if the tab name is in the path. + * returns nil if the tab name is not in the path. + + ## Examples + + iex> BlockScoutWeb.TabHelpers.tab_active?("token", "/page/0xSom3tH1ng/token") + true + + iex> BlockScoutWeb.TabHelpers.tab_active?("token", "/page/0xSom3tH1ng/token_transfer") + false + """ + def tab_active?("transactions", "/address/" <> "0x" <> <<_address_hash::binary-size(40)>>), do: true + + def tab_active?(tab_name, request_path) do + String.match?(request_path, ~r/\/\b#{tab_name}\b/) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/contract_view.ex new file mode 100644 index 0000000..0b90df1 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/contract_view.ex @@ -0,0 +1,6 @@ +defmodule BlockScoutWeb.Tokens.ContractView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Tokens.OverviewView + alias Explorer.Chain.Address +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 new file mode 100644 index 0000000..40d5f4e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex @@ -0,0 +1,133 @@ +defmodule BlockScoutWeb.Tokens.Helpers do + @moduledoc """ + Helper functions for interacting with `t:BlockScoutWeb.Chain.Token` attributes. + """ + + alias BlockScoutWeb.{AddressView, CurrencyHelpers} + alias Explorer.Chain.{Address, Token} + + @doc """ + Returns the token transfers' amount according to the token's type and decimals. + + When the token's type is ERC-20, then we are going to format the amount according to the token's + decimals considering 0 when the decimals is nil. Case the amount is nil, this function will + return the symbol `--`. + + 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) + end + + def token_transfer_amount(%{token: token, amount: amount, token_id: token_id}) do + do_token_transfer_amount(token, amount, nil, token_id, nil) + end + + defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, nil, _token_id, _token_ids) do + {:ok, "--"} + end + + defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, amount, _amounts, _token_id, _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 + {: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 + {:ok, :erc721_instance} + end + + defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, amounts, _token_id, token_ids) do + if amount do + {:ok, :erc1155_instance, CurrencyHelpers.format_according_to_decimals(amount, decimals)} + else + {:ok, :erc1155_instance, amounts, token_ids, decimals} + end + end + + defp do_token_transfer_amount(_token, _amount, _amounts, _token_id, _token_ids) do + nil + end + + def token_transfer_amount_for_api(%{ + token: token, + amount: amount, + amounts: amounts, + token_id: token_id, + token_ids: token_ids + }) do + do_token_transfer_amount_for_api(token, amount, amounts, token_id, token_ids) + end + + def token_transfer_amount_for_api(%{token: token, amount: amount, token_id: token_id}) do + do_token_transfer_amount_for_api(token, amount, nil, token_id, nil) + end + + defp do_token_transfer_amount_for_api(%Token{type: "ERC-20"}, nil, nil, _token_id, _token_ids) do + {:ok, nil} + end + + defp do_token_transfer_amount_for_api( + %Token{type: "ERC-20", decimals: decimals}, + amount, + _amounts, + _token_id, + _token_ids + ) do + {:ok, amount, decimals} + end + + defp do_token_transfer_amount_for_api(%Token{type: "ERC-721"}, _amount, _amounts, _token_id, _token_ids) do + {:ok, :erc721_instance} + end + + defp do_token_transfer_amount_for_api( + %Token{type: "ERC-1155", decimals: decimals}, + amount, + amounts, + _token_id, + 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_id, _token_ids) do + nil + end + + @doc """ + Returns the token's symbol. + + When the token's symbol is nil, the function will return the contract address hash. + """ + def token_symbol(%Token{symbol: nil, contract_address_hash: address_hash}) do + AddressView.short_hash_left_right(address_hash) + end + + def token_symbol(%Token{symbol: symbol}) do + symbol + end + + @doc """ + Returns the token's name. + + When the token's name is nil, the function will return the contract address hash. + """ + def token_name(%Token{} = token), do: build_token_name(token) + def token_name(%Address.Token{} = address_token), do: build_token_name(address_token) + + defp build_token_name(%{name: nil, contract_address_hash: address_hash}) do + AddressView.short_hash_left_right(address_hash) + end + + defp build_token_name(%{name: name}) do + name + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex new file mode 100644 index 0000000..2edfd93 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex @@ -0,0 +1,76 @@ +defmodule BlockScoutWeb.Tokens.HolderView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Tokens.OverviewView + alias Explorer.Chain.Token + + @doc """ + Checks if the total supply percentage must be shown. + + ## Examples + + iex> BlockScoutWeb.Tokens.HolderView.show_total_supply_percentage?(nil) + false + + iex> BlockScoutWeb.Tokens.HolderView.show_total_supply_percentage?(0) + false + + iex> BlockScoutWeb.Tokens.HolderView.show_total_supply_percentage?(100) + true + + """ + def show_total_supply_percentage?(nil), do: false + def show_total_supply_percentage?(total_supply), do: total_supply > 0 + + @doc """ + Calculates the percentage of the value from the given total supply. + + ## Examples + + iex> value = Decimal.new(200) + iex> total_supply = Decimal.new(1000) + iex> BlockScoutWeb.Tokens.HolderView.total_supply_percentage(value, total_supply) + "20.0000%" + + """ + def total_supply_percentage(_, 0), do: "N/A%" + + def total_supply_percentage(_, %Decimal{coef: 0}), do: "N/A%" + + def total_supply_percentage(value, total_supply) do + result = + value + |> Decimal.div(total_supply) + |> Decimal.mult(100) + |> Decimal.round(4) + |> Decimal.to_string() + + result <> "%" + end + + @doc """ + Formats the token balance value according to the Token's type. + + ## Examples + + iex> token = build(:token, type: "ERC-20", decimals: Decimal.new(2)) + iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(100000, nil, token) + "1,000" + + iex> token = build(:token, type: "ERC-721") + iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(1, nil, token) + 1 + + """ + def format_token_balance_value(value, _id, %Token{type: "ERC-20", decimals: decimals}) do + format_according_to_decimals(value, decimals) + end + + def format_token_balance_value(value, id, %Token{type: "ERC-1155", decimals: decimals}) do + to_string(format_according_to_decimals(value, decimals)) <> " TokenID " <> to_string(id) + end + + def format_token_balance_value(value, _id, _token) do + value + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/holder_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/holder_view.ex new file mode 100644 index 0000000..38cf207 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/holder_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.Tokens.Instance.HolderView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Tokens.Instance.OverviewView +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 new file mode 100644 index 0000000..758e6a3 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/metadata_view.ex @@ -0,0 +1,9 @@ +defmodule BlockScoutWeb.Tokens.Instance.MetadataView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Tokens.Instance.OverviewView + + def format_metadata(nil), do: "" + + 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 new file mode 100644 index 0000000..4a25a93 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex @@ -0,0 +1,199 @@ +defmodule BlockScoutWeb.Tokens.Instance.OverviewView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.CurrencyHelpers + alias Explorer.Chain + alias Explorer.Chain.{Address, SmartContract, Token} + alias Explorer.SmartContract.Helper + alias FileInfo + alias MIME + alias Path + + import BlockScoutWeb.APIDocsView, only: [blockscout_url: 1, blockscout_url: 2] + + @tabs ["token-transfers", "metadata"] + @stub_image "/images/controller.svg" + + def token_name?(%Token{name: nil}), do: false + def token_name?(%Token{name: _}), do: true + + def decimals?(%Token{decimals: nil}), do: false + def decimals?(%Token{decimals: _}), do: true + + def total_supply?(%Token{total_supply: nil}), do: false + def total_supply?(%Token{total_supply: _}), do: true + + def media_src(instance, high_quality_media? \\ nil) + def media_src(nil, _), do: @stub_image + + def media_src(instance, high_quality_media?) do + result = get_media_src(instance.metadata, high_quality_media?) + + if String.trim(result) == "", do: media_src(nil), else: result + end + + defp get_media_src(nil, _), do: media_src(nil) + + defp get_media_src(metadata, high_quality_media?) do + cond do + metadata["animation_url"] && high_quality_media? -> + retrieve_image(metadata["animation_url"]) + + metadata["image_url"] -> + retrieve_image(metadata["image_url"]) + + metadata["image"] -> + retrieve_image(metadata["image"]) + + metadata["properties"]["image"]["description"] -> + metadata["properties"]["image"]["description"] + + true -> + media_src(nil) + 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.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 + end + else + ext_with_dot = + media_src + |> Path.extname() + + "." <> ext = ext_with_dot + + ext + |> MIME.type() + end + + if mime_type do + basic_mime_type = mime_type |> String.split("/") |> Enum.at(0) + + basic_mime_type + else + nil + end + end + + def media_type(nil), do: nil + + def external_url(nil), do: nil + + def external_url(instance) do + result = + if instance.metadata && instance.metadata["external_url"] do + instance.metadata["external_url"] + else + external_url(nil) + end + + if !result || (result && String.trim(result)) == "", do: external_url(nil), else: result + end + + def total_supply_usd(token) do + tokens = CurrencyHelpers.divide_decimals(token.total_supply, token.decimals) + price = token.usd_value + Decimal.mult(tokens, price) + end + + 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)) + end + + def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false + + def qr_code(conn, token_id, hash) do + token_instance_path = token_instance_path(conn, :show, to_string(hash), to_string(token_id)) + + url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url] + api_path = url_params[:api_path] + path = url_params[:path] + + url_prefix = + if String.length(path) > 0 && path != "/" do + set_path = false + blockscout_url(set_path) + else + if String.length(api_path) > 0 && api_path != "/" do + is_api = true + set_path = true + blockscout_url(set_path, is_api) + else + set_path = false + blockscout_url(set_path) + end + end + + url = Path.join(url_prefix, token_instance_path) + + url + |> QRCode.to_png() + |> Base.encode64() + end + + def current_tab_name(request_path) do + @tabs + |> Enum.filter(&tab_active?(&1, request_path)) + |> tab_name() + end + + defp retrieve_image(image) when is_nil(image), do: @stub_image + + defp retrieve_image(image) when is_map(image) do + image["description"] + end + + defp retrieve_image(image) when is_list(image) do + image_url = image |> Enum.at(0) + retrieve_image(image_url) + end + + defp retrieve_image(image_url) do + image_url + |> URI.encode() + |> compose_ipfs_url() + end + + defp compose_ipfs_url(image_url) do + cond do + image_url =~ ~r/^ipfs:\/\/ipfs/ -> + "ipfs://ipfs" <> ipfs_uid = image_url + "https://ipfs.io/ipfs/" <> ipfs_uid + + image_url =~ ~r/^ipfs:\/\// -> + "ipfs://" <> ipfs_uid = image_url + "https://ipfs.io/ipfs/" <> ipfs_uid + + true -> + image_url + end + end + + defp tab_name(["token-transfers"]), do: gettext("Token Transfers") + defp tab_name(["metadata"]), do: gettext("Metadata") +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/transfer_view.ex new file mode 100644 index 0000000..2cf314e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/transfer_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.Tokens.Instance.TransferView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Tokens.Instance.OverviewView +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance_view.ex new file mode 100644 index 0000000..c18c5b5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance_view.ex @@ -0,0 +1,3 @@ +defmodule BlockScoutWeb.Tokens.InstanceView do + use BlockScoutWeb, :view +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex new file mode 100644 index 0000000..547d6dd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex @@ -0,0 +1,8 @@ +defmodule BlockScoutWeb.Tokens.InventoryView do + use BlockScoutWeb, :view + + import BlockScoutWeb.Tokens.Instance.OverviewView, only: [media_src: 1, media_type: 1] + + alias BlockScoutWeb.Tokens.OverviewView + alias Explorer.Chain +end 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 new file mode 100644 index 0000000..e258938 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -0,0 +1,85 @@ +defmodule BlockScoutWeb.Tokens.OverviewView do + use BlockScoutWeb, :view + + alias Explorer.{Chain, CustomContractsHelpers} + alias Explorer.Chain.{Address, SmartContract, Token} + alias Explorer.SmartContract.{Helper, Writer} + + alias BlockScoutWeb.{AccessHelpers, CurrencyHelpers, LayoutView} + + import BlockScoutWeb.AddressView, only: [from_address_hash: 1] + + @tabs ["token-transfers", "token-holders", "read-contract", "inventory"] + + def decimals?(%Token{decimals: nil}), do: false + def decimals?(%Token{decimals: _}), do: true + + def token_name?(%Token{name: nil}), do: false + def token_name?(%Token{name: _}), do: true + + def total_supply?(%Token{total_supply: nil}), do: false + def total_supply?(%Token{total_supply: _}), do: true + + @doc """ + Get the current tab name/title from the request path and possible tab names. + + The tabs on mobile are represented by a dropdown list, which has a title. This title is the + currently selected tab name. This function returns that name, properly gettext'ed. + + The list of possible tab names for this page is represented by the attribute @tab. + + Raises error if there is no match, so a developer of a new tab must include it in the list. + """ + def current_tab_name(request_path) do + @tabs + |> Enum.filter(&tab_active?(&1, request_path)) + |> tab_name() + end + + defp tab_name(["token-transfers"]), do: gettext("Token Transfers") + defp tab_name(["token-holders"]), do: gettext("Token Holders") + defp tab_name(["read-contract"]), do: gettext("Read Contract") + defp tab_name(["inventory"]), do: gettext("Inventory") + + def display_inventory?(%Token{type: "ERC-721"}), do: true + def display_inventory?(%Token{type: "ERC-1155"}), do: true + def display_inventory?(_), do: false + + 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)) + 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) + end + + def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false + + def smart_contract_with_write_functions?(%Token{ + contract_address: %Address{smart_contract: %SmartContract{}} = address + }) do + Enum.any?( + address.smart_contract.abi, + &Writer.write_function?(&1) + ) + end + + def smart_contract_with_write_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false + + @doc """ + Get the total value of the token supply in USD. + """ + def total_supply_usd(token) do + if Map.has_key?(token, :custom_cap) && token.custom_cap do + token.custom_cap + else + tokens = CurrencyHelpers.divide_decimals(token.total_supply, token.decimals) + price = token.usd_value + Decimal.mult(tokens, price) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex new file mode 100644 index 0000000..3ea5d84 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex @@ -0,0 +1,7 @@ +defmodule BlockScoutWeb.Tokens.TransferView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Tokens.OverviewView + alias Explorer.Chain + alias Explorer.Chain.Address +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens_view.ex new file mode 100644 index 0000000..704d7a9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens_view.ex @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.TokensView do + use BlockScoutWeb, :view + + alias Explorer.Chain.{Address, Token} + + def decimals?(%Token{decimals: nil}), do: false + def decimals?(%Token{decimals: _}), do: true + + def token_display_name(%Token{name: nil, symbol: nil}), do: "" + + def token_display_name(%Token{name: "", symbol: ""}), do: "" + + def token_display_name(%Token{name: name, symbol: nil}), do: name + + def token_display_name(%Token{name: name, symbol: ""}), do: name + + def token_display_name(%Token{name: nil, symbol: symbol}), do: symbol + + def token_display_name(%Token{name: "", symbol: symbol}), do: symbol + + def token_display_name(%Token{name: name, symbol: symbol}), do: "#{name} (#{symbol})" +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_internal_transaction_view.ex new file mode 100644 index 0000000..74ff604 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_internal_transaction_view.ex @@ -0,0 +1,4 @@ +defmodule BlockScoutWeb.TransactionInternalTransactionView do + use BlockScoutWeb, :view + @dialyzer :no_match +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_log_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_log_view.ex new file mode 100644 index 0000000..e494bbd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_log_view.ex @@ -0,0 +1,11 @@ +defmodule BlockScoutWeb.TransactionLogView do + use BlockScoutWeb, :view + @dialyzer :no_match + + alias Explorer.Chain.Log + import BlockScoutWeb.AddressView, only: [implementation_name: 1, primary_name: 1] + + def decode(log, transaction) do + Log.decode(log, transaction) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex new file mode 100644 index 0000000..3adab16 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_raw_trace_view.ex @@ -0,0 +1,18 @@ +defmodule BlockScoutWeb.TransactionRawTraceView do + use BlockScoutWeb, :view + @dialyzer :no_match + + alias Explorer.Chain.InternalTransaction + + def render("scripts.html", %{conn: conn}) do + render_scripts(conn, "raw-trace/code_highlighting.js") + end + + def raw_traces_with_lines(internal_transactions) do + internal_transactions + |> InternalTransaction.internal_transactions_to_raw() + |> Jason.encode!(pretty: true) + |> String.split("\n") + |> Enum.with_index(1) + end +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 0000000..27b5950 --- /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_token_transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex new file mode 100644 index 0000000..66999ad --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.TransactionTokenTransferView do + use BlockScoutWeb, :view + + alias Explorer.Chain +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 new file mode 100644 index 0000000..e5e7f99 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -0,0 +1,599 @@ +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 + alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction, Wei} + alias Explorer.Counters.AverageBlockTime + alias Explorer.ExchangeRates.Token + alias Timex.Duration + + import BlockScoutWeb.Gettext + 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", "state"] + + @token_burning_title "Token Burning" + @token_minting_title "Token Minting" + @token_transfer_title "Token Transfer" + @token_creation_title "Token Creation" + + @token_burning_type :token_burning + @token_minting_type :token_minting + @token_creation_type :token_spawning + @token_transfer_type :token_transfer + + defguardp is_transaction_type(mod) when mod in [InternalTransaction, Transaction] + + defdelegate formatted_timestamp(block), to: BlockView + + def block_number(%Transaction{block_number: nil}), do: gettext("Block Pending") + def block_number(%Transaction{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block] + def block_number(%Reward{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block] + + def block_timestamp(%Transaction{block_number: nil, inserted_at: time}), do: time + def block_timestamp(%Transaction{block: %Block{timestamp: time}}), do: time + def block_timestamp(%Reward{block: %Block{timestamp: time}}), do: time + + def value_transfer?(%Transaction{input: %{bytes: bytes}}) when bytes in [<<>>, nil] do + true + end + + def value_transfer?(_), do: false + + def token_transfer_type(transaction) do + transaction_with_transfers = Repo.preload(transaction, token_transfers: :token) + + token_transfers_filtered_by_block_hash = + transaction_with_transfers + |> Map.get(:token_transfers, []) + |> Enum.filter(fn token_transfer -> + token_transfer.block_hash == transaction.block_hash + end) + + transaction_with_transfers_filtered = + Map.put(transaction_with_transfers, :token_transfers, token_transfers_filtered_by_block_hash) + + type = Chain.transaction_token_transfer_type(transaction) + if type, do: {type, transaction_with_transfers_filtered}, else: {nil, transaction_with_transfers_filtered} + end + + def aggregate_token_transfers(token_transfers) do + %{ + transfers: {ft_transfers, nft_transfers}, + mintings: {ft_mintings, nft_mintings}, + burnings: {ft_burnings, nft_burnings}, + creations: {ft_creations, nft_creations} + } = + token_transfers + |> Enum.reduce( + %{ + transfers: {[], []}, + mintings: {[], []}, + burnings: {[], []}, + creations: {[], []} + }, + fn token_transfer, acc -> + token_transfer_type = Chain.get_token_transfer_type(token_transfer) + + case token_transfer_type do + :token_transfer -> + transfers = aggregate_reducer(token_transfer, acc.transfers) + + %{ + transfers: transfers, + mintings: acc.mintings, + burnings: acc.burnings, + creations: acc.creations + } + + :token_burning -> + burnings = aggregate_reducer(token_transfer, acc.burnings) + + %{ + transfers: acc.transfers, + mintings: acc.mintings, + burnings: burnings, + creations: acc.creations + } + + :token_minting -> + mintings = aggregate_reducer(token_transfer, acc.mintings) + + %{ + transfers: acc.transfers, + mintings: mintings, + burnings: acc.burnings, + creations: acc.creations + } + + :token_spawning -> + creations = aggregate_reducer(token_transfer, acc.creations) + + %{ + transfers: acc.transfers, + mintings: acc.mintings, + burnings: acc.burnings, + creations: creations + } + end + end + ) + + transfers = ft_transfers ++ nft_transfers + + mintings = ft_mintings ++ nft_mintings + + burnings = ft_burnings ++ nft_burnings + + creations = ft_creations ++ nft_creations + + %{transfers: transfers, mintings: mintings, burnings: burnings, creations: creations} + end + + defp aggregate_reducer(%{amount: amount, amounts: amounts} = token_transfer, {acc1, acc2}) + when is_nil(amount) and is_nil(amounts) do + new_entry = %{ + token: token_transfer.token, + amount: nil, + amounts: [], + token_id: token_transfer.token_id, + token_ids: [], + to_address_hash: token_transfer.to_address_hash, + from_address_hash: token_transfer.from_address_hash + } + + {acc1, [new_entry | acc2]} + end + + defp aggregate_reducer(%{amount: amount, amounts: amounts} = token_transfer, {acc1, acc2}) + when is_nil(amount) and not is_nil(amounts) do + new_entry = %{ + 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 + } + + {acc1, [new_entry | acc2]} + end + + defp aggregate_reducer(token_transfer, {acc1, acc2}) do + new_entry = %{ + token: token_transfer.token, + amount: token_transfer.amount, + amounts: [], + token_id: token_transfer.token_id, + token_ids: [], + to_address_hash: token_transfer.to_address_hash, + from_address_hash: token_transfer.from_address_hash + } + + existing_entry = + acc1 + |> Enum.find(fn entry -> + entry.to_address_hash == token_transfer.to_address_hash && + entry.from_address_hash == token_transfer.from_address_hash && + entry.token == token_transfer.token + end) + + new_acc1 = + if existing_entry do + acc1 + |> Enum.map(fn entry -> + if entry.to_address_hash == token_transfer.to_address_hash && + entry.from_address_hash == token_transfer.from_address_hash && + entry.token == token_transfer.token do + updated_entry = %{ + entry + | amount: Decimal.add(new_entry.amount, entry.amount) + } + + updated_entry + else + entry + end + end) + else + [new_entry | acc1] + end + + {new_acc1, acc2} + end + + def token_type_name(type) do + case type do + :erc20 -> gettext("ERC-20 ") + :erc721 -> gettext("ERC-721 ") + :erc1155 -> gettext("ERC-1155 ") + _ -> "" + end + end + + def processing_time_duration(%Transaction{block: nil}) do + :pending + end + + def processing_time_duration(%Transaction{earliest_processing_start: nil}) do + avg_time = AverageBlockTime.average_block_time() + + if avg_time == {:error, :disabled} do + :unknown + else + avg_time_in_secs = + avg_time + |> Duration.to_seconds() + + {:ok, "<= #{avg_time_in_secs} seconds"} + end + end + + def processing_time_duration(%Transaction{ + block: %Block{timestamp: end_time}, + earliest_processing_start: earliest_processing_start, + inserted_at: inserted_at + }) do + with {:ok, long_interval} <- humanized_diff(earliest_processing_start, end_time), + {:ok, short_interval} <- humanized_diff(inserted_at, end_time) do + {:ok, merge_intervals(short_interval, long_interval)} + else + _ -> + :ignore + end + end + + defp merge_intervals(short, long) when short == long, do: short + + defp merge_intervals(short, long) do + [short_time, short_unit] = String.split(short, " ") + [long_time, long_unit] = String.split(long, " ") + + if short_unit == long_unit do + short_time <> "-" <> long_time <> " " <> short_unit + else + short <> " - " <> long + end + end + + defp humanized_diff(left, right) do + left + |> Timex.diff(right, :milliseconds) + |> Duration.from_milliseconds() + |> Timex.format_duration(Explorer.Counters.AverageBlockTimeDurationFormat) + |> case do + {:error, _} = error -> error + duration -> {:ok, duration} + end + end + + def confirmations(%Transaction{block: block}, named_arguments) when is_list(named_arguments) do + case block do + %Block{consensus: true} -> + {:ok, confirmations} = Chain.confirmations(block, named_arguments) + BlockScoutWeb.Cldr.Number.to_string!(confirmations, format: "#,###") + + _ -> + 0 + end + end + + def confirmations_ds_name(blocks_amount_str) do + case Integer.parse(blocks_amount_str) do + {blocks_amount, ""} -> + if rem(blocks_amount, 10) == 1 do + "block" + else + "blocks" + end + + _ -> + "" + end + end + + def contract_creation?(%Transaction{to_address: nil}), do: true + + def contract_creation?(_), do: false + + def fee(%Transaction{} = transaction) do + {_, value} = Chain.fee(transaction, :wei) + value + end + + def format_gas_limit(gas) do + Number.to_string!(gas) + end + + def formatted_fee(%Transaction{} = transaction, opts) do + transaction + |> Chain.fee(:wei) + |> fee_to_denomination(opts) + |> case do + {:actual, value} -> value + {:maximum, value} -> "#{gettext("Max of")} #{value}" + end + end + + def transaction_status(transaction) do + Chain.transaction_to_status(transaction) + end + + def transaction_revert_reason(transaction) do + transaction |> Chain.transaction_to_revert_reason() |> decoded_revert_reason(transaction) + end + + def get_pure_transaction_revert_reason(nil), do: nil + + def get_pure_transaction_revert_reason(transaction), do: Chain.transaction_to_revert_reason(transaction) + + def empty_exchange_rate?(exchange_rate) do + Token.null?(exchange_rate) + end + + def formatted_status(status) do + case status do + :pending -> gettext("Unconfirmed") + _ -> gettext("Confirmed") + end + end + + def formatted_result(status) do + case status do + :pending -> gettext("Pending") + :awaiting_internal_transactions -> gettext("(Awaiting internal transactions for status)") + :success -> gettext("Success") + {:error, :awaiting_internal_transactions} -> gettext("Error: (Awaiting internal transactions for reason)") + # The pool of possible error reasons is unknown or even if it is enumerable, so we can't translate them + {:error, reason} when is_binary(reason) -> gettext("Error: %{reason}", reason: reason) + end + end + + def from_or_to_address?(_token_transfer, nil), do: false + + def from_or_to_address?(%{from_address_hash: from_hash, to_address_hash: to_hash}, %Address{hash: hash}) do + from_hash == hash || to_hash == hash + end + + def gas(%type{gas: gas}) when is_transaction_type(type) do + BlockScoutWeb.Cldr.Number.to_string!(gas) + end + + def skip_decoding?(transaction) do + contract_creation?(transaction) || value_transfer?(transaction) + end + + def decoded_input_data(transaction) do + Transaction.decoded_input_data(transaction) + end + + def decoded_revert_reason(revert_reason, transaction) do + Transaction.decoded_revert_reason(transaction, revert_reason) + end + + @doc """ + Converts a transaction's gas price to a displayable value. + """ + def gas_price(%Transaction{gas_price: gas_price}, unit) when unit in ~w(wei gwei ether)a do + format_wei_value(gas_price, unit) + end + + def gas_used(%Transaction{gas_used: nil}), do: gettext("Pending") + + def gas_used(%Transaction{gas_used: gas_used}) do + Number.to_string!(gas_used) + end + + def gas_used_perc(%Transaction{gas_used: nil}), do: nil + + def gas_used_perc(%Transaction{gas_used: gas_used, gas: gas}) do + if Decimal.compare(gas, 0) == :gt do + gas_used + |> Decimal.div(gas) + |> Decimal.mult(100) + |> Decimal.round(2) + |> Number.to_string!() + else + nil + end + end + + def hash(%Transaction{hash: hash}) do + to_string(hash) + end + + def involves_contract?(%Transaction{from_address: from_address, to_address: to_address}) do + AddressView.contract?(from_address) || AddressView.contract?(to_address) + end + + def involves_token_transfers?(%Transaction{token_transfers: []}), do: false + def involves_token_transfers?(%Transaction{token_transfers: transfers}) when is_list(transfers), do: true + def involves_token_transfers?(_), do: false + + def qr_code(%Transaction{hash: hash}) do + hash + |> to_string() + |> QRCode.to_png() + |> Base.encode64() + end + + def status_class(transaction) do + case Chain.transaction_to_status(transaction) do + :pending -> "tile-status--pending" + :awaiting_internal_transactions -> "tile-status--awaiting-internal-transactions" + :success -> "tile-status--success" + {:error, :awaiting_internal_transactions} -> "tile-status--error--awaiting-internal-transactions" + {:error, reason} when is_binary(reason) -> "tile-status--error--reason" + end + end + + # This is the address to be shown in the to field + def to_address_hash(%Transaction{to_address_hash: nil, created_contract_address_hash: address_hash}), + do: address_hash + + def to_address_hash(%Transaction{to_address_hash: address_hash}), do: address_hash + + def transaction_display_type(%Transaction{} = transaction) do + cond do + involves_token_transfers?(transaction) -> + token_transfer_type = get_transaction_type_from_token_transfers(transaction.token_transfers) + + case token_transfer_type do + @token_minting_type -> gettext(@token_minting_title) + @token_burning_type -> gettext(@token_burning_title) + @token_creation_type -> gettext(@token_creation_title) + @token_transfer_type -> gettext(@token_transfer_title) + end + + contract_creation?(transaction) -> + gettext("Contract Creation") + + involves_contract?(transaction) -> + gettext("Contract Call") + + true -> + gettext("Transaction") + end + end + + def type_suffix(%Transaction{} = transaction) do + cond do + involves_token_transfers?(transaction) -> "token-transfer" + contract_creation?(transaction) -> "contract-creation" + involves_contract?(transaction) -> "contract-call" + true -> "transaction" + end + end + + @doc """ + Converts a transaction's Wei value to Ether and returns a formatted display value. + + ## Options + + * `:include_label` - Boolean. Defaults to true. Flag for displaying unit with value. + """ + def value(%mod{value: value}, opts \\ []) when is_transaction_type(mod) do + include_label? = Keyword.get(opts, :include_label, true) + format_wei_value(value, :ether, include_unit_label: include_label?) + end + + def format_wei_value(value) do + format_wei_value(value, :ether, include_unit_label: false) + end + + defp fee_to_denomination({fee_type, fee}, opts) do + denomination = Keyword.get(opts, :denomination) + include_label? = Keyword.get(opts, :include_label, true) + {fee_type, format_wei_value(Wei.from(fee, :wei), denomination, include_unit_label: include_label?)} + end + + @doc """ + Get the current tab name/title from the request path and possible tab names. + + The tabs on mobile are represented by a dropdown list, which has a title. This title is the currently selected tab name. This function returns that name, properly gettext'ed. + + The list of possible tab names for this page is represented by the attribute @tab. + + Raises an error if there is no match, so a developer of a new tab must include it in the list. + + """ + def current_tab_name(request_path) do + @tabs + |> Enum.filter(&TabHelpers.tab_active?(&1, request_path)) + |> tab_name() + end + + defp tab_name(["token-transfers"]), do: gettext("Token Transfers") + 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 = + token_transfers + |> Enum.map(fn token_transfer -> + Chain.get_token_transfer_type(token_transfer) + end) + + burnings_count = + Enum.count(token_transfers_types, fn token_transfers_type -> token_transfers_type == @token_burning_type end) + + mintings_count = + Enum.count(token_transfers_types, fn token_transfers_type -> token_transfers_type == @token_minting_type end) + + creations_count = + Enum.count(token_transfers_types, fn token_transfers_type -> token_transfers_type == @token_creation_type end) + + cond do + Enum.count(token_transfers_types) == burnings_count -> @token_burning_type + Enum.count(token_transfers_types) == mintings_count -> @token_minting_type + Enum.count(token_transfers_types) == creations_count -> @token_creation_type + true -> @token_transfer_type + end + end + + defp show_tenderly_link? do + System.get_env("SHOW_TENDERLY_LINK") == "true" + end + + defp tenderly_chain_path do + System.get_env("TENDERLY_CHAIN_PATH") || "/" + end + + def get_max_length do + string_value = Application.get_env(:block_scout_web, :max_length_to_show_string_without_trimming) + + case Integer.parse(string_value) do + {integer, ""} -> integer + _ -> 2040 + end + end + + def trim(length, string) do + %{show: String.slice(string, 0..length), hide: String.slice(string, (length + 1)..String.length(string))} + end + + defp template_to_string(template) when is_list(template) do + template_to_string(Enum.at(template, 1)) + end + + defp template_to_string(template) when is_tuple(template) do + safe_to_string(template) + end + + # Function decodes revert reason of the transaction + @spec decoded_revert_reason(Transaction.t() | nil) :: binary() | nil + def decoded_revert_reason(transaction) do + revert_reason = get_pure_transaction_revert_reason(transaction) + + case revert_reason do + "0x" <> hex_part -> + proccess_hex_revert_reason(hex_part) + + hex_part -> + proccess_hex_revert_reason(hex_part) + end + end + + # Function converts hex revert reason to the binary + @spec proccess_hex_revert_reason(nil) :: nil + defp proccess_hex_revert_reason(nil), do: nil + + @spec proccess_hex_revert_reason(binary()) :: binary() + defp proccess_hex_revert_reason(hex_revert_reason) do + case Integer.parse(hex_revert_reason, 16) do + {number, ""} -> + :binary.encode_unsigned(number) + + _ -> + hex_revert_reason + end + end +end 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 0000000..e541791 --- /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/wei_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex new file mode 100644 index 0000000..1239f32 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex @@ -0,0 +1,79 @@ +defmodule BlockScoutWeb.WeiHelpers do + @moduledoc """ + Helper functions for interacting with `t:Explorer.Chain.Wei.t/0` values. + """ + + import BlockScoutWeb.Gettext + + alias BlockScoutWeb.CldrHelper + alias Explorer.Chain.Wei + + @valid_units ~w(wei gwei ether)a + + @type format_option :: {:include_unit_label, boolean()} + + @type format_options :: [format_option()] + + @doc """ + Converts a `t:Explorer.Wei.t/0` value to the specified unit including a + translated unit label. + + ## Supported Formatting Options + + The third argument allows for keyword options to be passed for formatting the + converted number. + + * `:include_unit_label` - Boolean (Defaults to `true`). Flag for if the unit + label should be included in the returned string + + ## Examples + + iex> format_wei_value(%Wei{value: Decimal.new(1)}, :wei) + "1 Wei" + + iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 12)}, :gwei) + "10,000 Gwei" + + iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 21)}, :ether) + "10,000 ETH" + + # With formatting options + + iex> format_wei_value( + ...> %Wei{value: Decimal.new(1000500000000000000)}, + ...> :ether + ...> ) + "1.0005 ETH" + + iex> format_wei_value( + ...> %Wei{value: Decimal.new(10)}, + ...> :wei, + ...> include_unit_label: false + ...> ) + "10" + """ + @spec format_wei_value(Wei.t(), Wei.unit(), format_options()) :: String.t() + def format_wei_value(%Wei{} = wei, unit, options \\ []) when unit in @valid_units do + converted_value = + wei + |> Wei.to(unit) + + formatted_value = + if Decimal.compare(converted_value, 1_000_000_000_000) == :gt do + CldrHelper.Number.to_string!(converted_value, format: "0.###E+0") + else + CldrHelper.Number.to_string!(converted_value, format: "#,##0.##################") + end + + if Keyword.get(options, :include_unit_label, true) do + display_unit = display_unit(unit) + "#{formatted_value} #{display_unit}" + else + formatted_value + end + end + + defp display_unit(:wei), do: gettext("Wei") + defp display_unit(:gwei), do: gettext("Gwei") + 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 new file mode 100644 index 0000000..e03bd10 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex @@ -0,0 +1,499 @@ +defmodule BlockScoutWeb.WebRouter do + @moduledoc """ + Router for web app + """ + use BlockScoutWeb, :router + require Ueberauth + + alias BlockScoutWeb.Plug.CheckAccountWeb + + pipeline :browser do + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(BlockScoutWeb.CSPHeader) + 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) + end + + # Allows Iframes (read-only routes) + scope "/", BlockScoutWeb do + pipe_through([:browser, BlockScoutWeb.Plug.AllowIframe]) + + resources("/", ChainController, only: [:show], singleton: true, as: :chain) + + resources("/market-history-chart", Chain.MarketHistoryChartController, + only: [:show], + singleton: true + ) + + resources("/transaction-history-chart", Chain.TransactionHistoryChartController, + only: [:show], + singleton: true + ) + + resources "/block", BlockController, only: [:show], param: "hash_or_number" do + resources("/transactions", BlockTransactionController, only: [:index], as: :transaction) + end + + resources("/blocks", BlockController, as: :blocks, only: [:index]) + + resources "/blocks", BlockController, + as: :block_secondary, + only: [:show], + param: "hash_or_number" do + resources("/transactions", BlockTransactionController, only: [:index], as: :transaction) + end + + get("/reorgs", BlockController, :reorg, as: :reorg) + + get("/uncles", BlockController, :uncle, as: :uncle) + + resources("/pending-transactions", PendingTransactionController, only: [:index]) + + resources("/recent-transactions", RecentTransactionsController, only: [:index]) + + resources("/verified-contracts", VerifiedContractsController, only: [:index]) + + get("/txs", TransactionController, :index) + + resources "/tx", TransactionController, only: [:show] do + resources( + "/internal-transactions", + TransactionInternalTransactionController, + only: [:index], + as: :internal_transaction + ) + + resources( + "/raw-trace", + TransactionRawTraceController, + only: [:index], + as: :raw_trace + ) + + resources("/logs", TransactionLogController, only: [:index], as: :log) + + resources("/token-transfers", TransactionTokenTransferController, + only: [:index], + as: :token_transfer + ) + + resources("/state", TransactionStateController, + only: [:index], + as: :state + ) + end + + resources("/accounts", AddressController, only: [:index]) + + resources("/tokens", TokensController, only: [:index]) + + resources "/address", AddressController, only: [:show] do + resources("/transactions", AddressTransactionController, only: [:index], as: :transaction) + + resources( + "/internal-transactions", + AddressInternalTransactionController, + only: [:index], + as: :internal_transaction + ) + + resources( + "/validations", + AddressValidationController, + only: [:index], + as: :validation + ) + + resources( + "/contracts", + AddressContractController, + only: [:index], + as: :contract + ) + + resources( + "/decompiled-contracts", + AddressDecompiledContractController, + only: [:index], + as: :decompiled_contract + ) + + resources( + "/logs", + AddressLogsController, + only: [:index], + as: :logs + ) + + resources( + "/contract_verifications", + AddressContractVerificationController, + only: [:new], + as: :verify_contract + ) + + resources( + "/verify-via-flattened-code", + AddressContractVerificationViaFlattenedCodeController, + only: [:new], + as: :verify_contract_via_flattened_code + ) + + resources( + "/verify-via-metadata-json", + AddressContractVerificationViaJsonController, + only: [:new], + as: :verify_contract_via_json + ) + + resources( + "/verify-via-standard-json-input", + AddressContractVerificationViaStandardJsonInputController, + only: [:new], + 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, + only: [:new], + as: :verify_vyper_contract + ) + + resources( + "/read-contract", + AddressReadContractController, + only: [:index, :show], + as: :read_contract + ) + + resources( + "/read-proxy", + AddressReadProxyController, + only: [:index, :show], + as: :read_proxy + ) + + resources( + "/write-contract", + AddressWriteContractController, + only: [:index, :show], + as: :write_contract + ) + + resources( + "/write-proxy", + AddressWriteProxyController, + only: [:index, :show], + as: :write_proxy + ) + + resources( + "/token-transfers", + AddressTokenTransferController, + only: [:index], + as: :token_transfers + ) + + resources("/tokens", AddressTokenController, only: [:index], as: :token) do + resources( + "/token-transfers", + AddressTokenTransferController, + only: [:index], + as: :transfers + ) + end + + resources( + "/token-balances", + AddressTokenBalanceController, + only: [:index], + as: :token_balance + ) + + resources( + "/coin-balances", + AddressCoinBalanceController, + only: [:index], + as: :coin_balance + ) + + resources( + "/coin-balances/by-day", + AddressCoinBalanceByDayController, + only: [:index], + as: :coin_balance_by_day + ) + end + + resources "/token", Tokens.TokenController, only: [:show], as: :token do + resources( + "/token-transfers", + Tokens.TransferController, + only: [:index], + as: :transfer + ) + + resources( + "/read-contract", + Tokens.ContractController, + only: [:index], + as: :read_contract + ) + + resources( + "/write-contract", + Tokens.ContractController, + only: [:index], + as: :write_contract + ) + + resources( + "/read-proxy", + Tokens.ContractController, + only: [:index], + as: :read_proxy + ) + + resources( + "/write-proxy", + Tokens.ContractController, + only: [:index], + as: :write_proxy + ) + + resources( + "/token-holders", + Tokens.HolderController, + only: [:index], + as: :holder + ) + + resources( + "/inventory", + Tokens.InventoryController, + only: [:index], + as: :inventory + ) + + resources( + "/instance", + Tokens.InstanceController, + only: [:show], + as: :instance + ) do + resources( + "/token-transfers", + Tokens.Instance.TransferController, + only: [:index], + as: :transfer + ) + + resources( + "/metadata", + Tokens.Instance.MetadataController, + only: [:index], + as: :metadata + ) + + resources( + "/token-holders", + Tokens.Instance.HolderController, + only: [:index], + as: :holder + ) + end + end + + resources "/tokens", Tokens.TokenController, only: [:show], as: :token_secondary do + resources( + "/token-transfers", + Tokens.TransferController, + only: [:index], + as: :transfer + ) + + resources( + "/read-contract", + Tokens.ContractController, + only: [:index], + as: :read_contract + ) + + resources( + "/write-contract", + Tokens.ContractController, + only: [:index], + as: :write_contract + ) + + resources( + "/read-proxy", + Tokens.ContractController, + only: [:index], + as: :read_proxy + ) + + resources( + "/write-proxy", + Tokens.ContractController, + only: [:index], + as: :write_proxy + ) + + resources( + "/token-holders", + Tokens.HolderController, + only: [:index], + as: :holder + ) + + resources( + "/inventory", + Tokens.InventoryController, + only: [:index], + as: :inventory + ) + + resources( + "/instance", + Tokens.InstanceController, + only: [:show], + as: :instance + ) do + resources( + "/token-transfers", + Tokens.Instance.TransferController, + only: [:index], + as: :transfer + ) + + resources( + "/metadata", + Tokens.Instance.MetadataController, + only: [:index], + as: :metadata + ) + + resources( + "/token-holders", + Tokens.Instance.HolderController, + only: [:index], + as: :holder + ) + end + end + + resources( + "/smart-contracts", + SmartContractController, + only: [:index, :show], + as: :smart_contract + ) + + get("/address-counters", AddressController, :address_counters) + + get("/search", ChainController, :search) + + get("/search-logs", AddressLogsController, :search_logs) + + get("/search-results", SearchController, :search_results) + + get("/search-verified-contracts", VerifiedContractsController, :search_verified_contracts) + + get("/csv-export", CsvExportController, :index) + + get("/transactions-csv", AddressTransactionController, :transactions_csv) + + get("/token-autocomplete", ChainController, :token_autocomplete) + + get("/token-transfers-csv", AddressTransactionController, :token_transfers_csv) + + get("/internal-transactions-csv", AddressTransactionController, :internal_transactions_csv) + + get("/logs-csv", AddressTransactionController, :logs_csv) + + get("/chain-blocks", ChainController, :chain_blocks, as: :chain_blocks) + + get("/token-counters", Tokens.TokenController, :token_counters) + + get("/*path", PageNotFoundController, :index) + end +end diff --git a/apps/block_scout_web/lib/phoenix/html/safe.ex b/apps/block_scout_web/lib/phoenix/html/safe.ex new file mode 100644 index 0000000..cddc3d5 --- /dev/null +++ b/apps/block_scout_web/lib/phoenix/html/safe.ex @@ -0,0 +1,32 @@ +alias Explorer.Chain +alias Explorer.Chain.{Address, Block, Data, Hash, Transaction} + +defimpl Phoenix.HTML.Safe, for: Address do + def to_iodata(%@for{} = address) do + @for.checksum(address, true) + end +end + +defimpl Phoenix.HTML.Safe, for: Transaction do + def to_iodata(%@for{hash: hash}) do + @protocol.to_iodata(hash) + end +end + +defimpl Phoenix.HTML.Safe, for: Block do + def to_iodata(%@for{number: number}) do + @protocol.to_iodata(number) + end +end + +defimpl Phoenix.HTML.Safe, for: Data do + def to_iodata(data) do + Chain.data_to_iodata(data) + end +end + +defimpl Phoenix.HTML.Safe, for: Hash do + def to_iodata(hash) do + Chain.hash_to_iodata(hash) + end +end diff --git a/apps/block_scout_web/lib/phoenix/param.ex b/apps/block_scout_web/lib/phoenix/param.ex new file mode 100644 index 0000000..e22fcac --- /dev/null +++ b/apps/block_scout_web/lib/phoenix/param.ex @@ -0,0 +1,29 @@ +alias Explorer.Chain.{Address, Block, Hash, Transaction} + +defimpl Phoenix.Param, for: Transaction do + def to_param(%@for{hash: hash}) do + @protocol.to_param(hash) + end +end + +defimpl Phoenix.Param, for: Address do + def to_param(%@for{} = address) do + @for.checksum(address) + end +end + +defimpl Phoenix.Param, for: Block do + def to_param(%@for{consensus: true, number: number}) do + to_string(number) + end + + def to_param(%@for{consensus: false, hash: hash}) do + to_string(hash) + end +end + +defimpl Phoenix.Param, for: Hash do + def to_param(hash) do + to_string(hash) + end +end diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs new file mode 100644 index 0000000..a166d2a --- /dev/null +++ b/apps/block_scout_web/mix.exs @@ -0,0 +1,167 @@ +defmodule BlockScoutWeb.Mixfile do + use Mix.Project + + def project do + [ + aliases: aliases(), + app: :block_scout_web, + build_path: "../../_build", + config_path: "../../config/config.exs", + deps: deps(), + deps_path: "../../deps", + description: "Web interface for BlockScout.", + dialyzer: [ + plt_add_deps: :transitive, + ignore_warnings: "../../.dialyzer-ignore" + ], + elixir: "~> 1.13", + elixirc_paths: elixirc_paths(Mix.env()), + lockfile: "../../mix.lock", + package: package(), + preferred_cli_env: [ + credo: :test, + dialyzer: :test + ], + start_permanent: Mix.env() == :prod, + version: "4.1.8" + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {BlockScoutWeb.Application, []}, + extra_applications: extra_applications() + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["test/support", "test/block_scout_web/features/pages"] ++ elixirc_paths() + defp elixirc_paths(_), do: elixirc_paths() + defp elixirc_paths, do: ["lib"] + + defp extra_applications, + do: [ + :ueberauth_auth0, + :logger, + :runtime_tools + ] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + # GraphQL toolkit + {:absinthe, "~> 1.5"}, + # Integrates Absinthe subscriptions with Phoenix + {:absinthe_phoenix, "~> 2.0.0"}, + # Plug support for Absinthe + {: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, "~> 2.1", only: :test}, + # To add (CORS)(https://www.w3.org/TR/cors/) + {:cors_plug, "~> 3.0"}, + {:credo, "~> 1.5", only: :test, runtime: false}, + # For Absinthe to load data in batches + {:dataloader, "~> 1.0.0"}, + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, + # Need until https://github.com/absinthe-graphql/absinthe_relay/pull/125 is released, then can be removed + # The current `absinthe_relay` is compatible though as shown from that PR + {:ecto, "~> 3.3", override: true}, + {:ex_cldr, "~> 2.7"}, + {:ex_cldr_numbers, "~> 2.6"}, + {:ex_cldr_units, "~> 3.13"}, + {:cldr_utils, "~> 2.3"}, + {:ex_machina, "~> 2.1", only: [:test]}, + {:explorer, in_umbrella: true}, + {:exvcr, "~> 0.10", only: :test}, + {:file_info, "~> 0.0.4"}, + # HTML CSS selectors for Phoenix controller tests + {:floki, "~> 0.31"}, + {:flow, "~> 1.2"}, + {:gettext, "~> 0.20.0"}, + {:hammer, "~> 6.0"}, + {:httpoison, "~> 1.6"}, + {:indexer, in_umbrella: true, runtime: false}, + # JSON parser and generator + {:jason, "~> 1.3"}, + {: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.7.0"}, + {:mock, "~> 0.3.0", only: [:test], runtime: false}, + {:number, "~> 1.0.1"}, + {:phoenix, "== 1.5.13"}, + {:phoenix_ecto, "~> 4.1"}, + {: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 + # https://github.com/michalmuskala/jason/issues/15 + {:poison, "~> 4.0.1"}, + {:postgrex, ">= 0.0.0"}, + # For compatibility with `prometheus_process_collector`, which hasn't been updated yet + {:prometheus, "~> 4.0", override: true}, + # Gather methods for Phoenix requests + {:prometheus_phoenix, "~> 1.2"}, + # Expose metrics from URL Prometheus server can scrape + {:prometheus_plugs, "~> 1.1"}, + # OS process metrics for Prometheus + {:prometheus_process_collector, "~> 1.3"}, + {:remote_ip, "~> 1.0"}, + {:qrcode, "~> 0.1.0"}, + {:sobelow, ">= 0.7.0", only: [:dev, :test], runtime: false}, + # Tracing + {:spandex, "~> 3.0"}, + # `:spandex` integration with Datadog + {:spandex_datadog, "~> 1.0"}, + # `:spandex` tracing of `:phoenix` + {:spandex_phoenix, "~> 1.0"}, + {:timex, "~> 3.7.1"}, + {: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.9.1"}, + {:ueberauth, "~> 0.7"}, + {:ueberauth_auth0, "~> 2.0"}, + {:bureaucrat, "~> 0.2.9", only: :test} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to create, migrate and run the seeds file at once: + # + # $ mix ecto.setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + compile: "compile --warnings-as-errors", + "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], + "ecto.reset": ["ecto.drop", "ecto.setup"], + test: [ + "ecto.create --quiet", + "ecto.migrate", + # to match behavior of `mix test` from project root, which needs to not start applications for `indexer` to + # prevent its supervision tree from starting, which is undesirable in test + "test --no-start" + ] + ] + end + + defp package do + [ + maintainers: ["Blockscout"], + licenses: ["GPL 3.0"], + links: %{"GitHub" => "https://github.com/blockscout/blockscout"} + ] + end +end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot new file mode 100644 index 0000000..c2cd456 --- /dev/null +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -0,0 +1,3426 @@ +#: 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] "" + +#: 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] "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +#, elixir-autogen, elixir-format +msgid " is recommended." +msgstr "" + +#: lib/block_scout_web/templates/address/_metatags.html.eex:3 +#, elixir-autogen, elixir-format +msgid "%{address} - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:12 +#, elixir-autogen, elixir-format +msgid "%{block_type} Details" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:55 +#, elixir-autogen, elixir-format +msgid "%{block_type} Height" +msgstr "" + +#: lib/block_scout_web/templates/block/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "%{block_type}s" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:85 +#, elixir-autogen, elixir-format +msgid "%{count} Transaction" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "%{subnetwork} %{network} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/layout/_default_title.html.eex:2 +#, elixir-autogen, elixir-format +msgid "%{subnetwork} Explorer - BlockScout" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:350 +#, elixir-autogen, elixir-format +msgid "(Awaiting internal transactions for status)" +msgstr "" + +#: 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:104 +#, elixir-autogen, elixir-format +msgid "(query)" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:230 +#, elixir-autogen, elixir-format +msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:97 +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:23 +#, elixir-autogen, elixir-format +msgid "A block producer who successfully included the block onto the blockchain." +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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/index.html.eex:4 +#, elixir-autogen, elixir-format +msgid "API Documentation" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 +#, elixir-autogen, elixir-format +msgid "API endpoints for the %{subnetwork}" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "API for the %{subnetwork} - BlockScout" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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:82 +#: 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:93 +#: 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/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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:217 +#, elixir-autogen, elixir-format +msgid "Address (external or contract) receiving the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:199 +#, elixir-autogen, elixir-format +msgid "Address (external or contract) sending the transaction." +msgstr "" + +#: 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:150 +#, elixir-autogen, elixir-format +msgid "Address balance in" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 +#, elixir-autogen, elixir-format +msgid "Address of the token contract" +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 "" + +#: 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/_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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#, elixir-autogen, elixir-format +msgid "All metadata displayed below is from that contract. In order to verify current contract, click" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:175 +#, elixir-autogen, elixir-format +msgid "All tokens in the account and total value." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:411 +#, elixir-autogen, elixir-format +msgid "Amount of" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#, elixir-autogen, elixir-format +msgid "Apps" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 +#: lib/block_scout_web/templates/transaction/not_found.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Back Home" +msgstr "" + +#: 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 "" + +#: 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:151 +#: 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 "" + +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Balances" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:207 +#, elixir-autogen, elixir-format +msgid "Base Fee per Gas" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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:158 +#, elixir-autogen, elixir-format +msgid "Block" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Block #%{number}" +msgstr "" + +#: lib/block_scout_web/templates/block/_metatags.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Block %{block_number} - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/block_transaction/404.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Block Details" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:53 +#, elixir-autogen, elixir-format +msgid "Block Height" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:43 +#, elixir-autogen, elixir-format +msgid "Block Mined, awaiting import..." +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:34 +#, elixir-autogen, elixir-format +msgid "Block Pending" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/block_transaction_view.ex:15 +#, elixir-autogen, elixir-format +msgid "Block not found, please try again later." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:157 +#, elixir-autogen, elixir-format +msgid "Block number containing the transaction." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:258 +#, elixir-autogen, elixir-format +msgid "Block number in which the address was updated." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/show.html.eex:153 +#: 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 "" + +#: lib/block_scout_web/templates/layout/app.html.eex:42 +#, elixir-autogen, elixir-format +msgid "Blocks Indexed" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:48 +#: lib/block_scout_web/templates/address/overview.html.eex:276 +#: lib/block_scout_web/templates/address_validation/index.html.eex:11 +#: lib/block_scout_web/views/address_view.ex:374 +#, elixir-autogen, elixir-format +msgid "Blocks Validated" +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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:65 +#, elixir-autogen, elixir-format +msgid "CRC Worth" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2 +#, elixir-autogen, elixir-format +msgid "CSV" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:22 +#, elixir-autogen, elixir-format +msgid "Call Code" +msgstr "" + +#: 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:111 +#: 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/api_docs/_eth_rpc_item.html.eex:54 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Chat (#blockscout)" +msgstr "" + +#: lib/block_scout_web/views/block_view.ex:65 +#, elixir-autogen, elixir-format +msgid "Chore Reward" +msgstr "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Close" +msgstr "" + +#: 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:367 +#, elixir-autogen, elixir-format +msgid "Code" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:34 +#: lib/block_scout_web/views/address_view.ex:373 +#, elixir-autogen, elixir-format +msgid "Coin Balance History" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Collapse" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:65 +#, elixir-autogen, elixir-format +msgid "Compiler version" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:343 +#, elixir-autogen, elixir-format +msgid "Confirmed" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:124 +#, elixir-autogen, elixir-format +msgid "Confirmed by " +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:190 +#, elixir-autogen, elixir-format +msgid "Confirmed within" +msgstr "" + +#: 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:16 +#, elixir-autogen, elixir-format +msgid "Connection Lost" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Connection Lost, click to load newer transactions" +msgstr "" + +#: lib/block_scout_web/templates/address_validation/index.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Connection Lost, click to load newer validations" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:90 +#, elixir-autogen, elixir-format +msgid "Constructor Arguments" +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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:122 +#, elixir-autogen, elixir-format +msgid "Contract ABI" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 +#: 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 "" + +#: lib/block_scout_web/views/transaction_view.ex:458 +#, elixir-autogen, elixir-format +msgid "Contract Call" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:455 +#, elixir-autogen, elixir-format +msgid "Contract Creation" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:138 +#: lib/block_scout_web/templates/address_contract/index.html.eex:153 +#, elixir-autogen, elixir-format +msgid "Contract Creation Code" +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:80 +#, elixir-autogen, elixir-format +msgid "Contract Libraries" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:25 +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:57 +#, elixir-autogen, elixir-format +msgid "Contract name:" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:100 +#, elixir-autogen, elixir-format +msgid "Contract source code" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:144 +#, elixir-autogen, elixir-format +msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:40 +#, elixir-autogen, elixir-format +msgid "Contribute" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:124 +#, elixir-autogen, elixir-format +msgid "Copy ABI" +msgstr "" + +#: 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 "" + +#: 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:140 +#: lib/block_scout_web/templates/address_contract/index.html.eex:156 +#, elixir-autogen, elixir-format +msgid "Copy Contract Creation Code" +msgstr "" + +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Copy Decompiled Contract Code" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:177 +#: lib/block_scout_web/templates/address_contract/index.html.eex:187 +#, elixir-autogen, elixir-format +msgid "Copy Deployed ByteCode" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Copy Metadata" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Copy Raw Trace" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:102 +#: lib/block_scout_web/templates/address_contract/index.html.eex:113 +#, elixir-autogen, elixir-format +msgid "Copy Source Code" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:87 +#, elixir-autogen, elixir-format +msgid "Copy Transaction Hash" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:88 +#, elixir-autogen, elixir-format +msgid "Copy Txn Hash" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:472 +#, elixir-autogen, elixir-format +msgid "Copy Txn Hex Input" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:478 +#, elixir-autogen, elixir-format +msgid "Copy Txn UTF-8 Input" +msgstr "" + +#: 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/overview.html.eex:477 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Copy Value" +msgstr "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:25 +#, elixir-autogen, elixir-format +msgid "Create" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/overview.html.eex:102 +#, elixir-autogen, elixir-format +msgid "Creator" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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/transaction_log/_logs.html.eex:121 +#, elixir-autogen, elixir-format +msgid "Data" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:70 +#, elixir-autogen, elixir-format +msgid "Date & time at which block was produced." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 +#, elixir-autogen, elixir-format +msgid "Decimals" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Decoded" +msgstr "" + +#: lib/block_scout_web/views/address_view.ex:368 +#, elixir-autogen, elixir-format +msgid "Decompiled Code" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:75 +#, elixir-autogen, elixir-format +msgid "Decompiled code" +msgstr "" + +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "Decompiled contract code" +msgstr "" + +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Decompiler version" +msgstr "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:23 +#, elixir-autogen, elixir-format +msgid "Delegate Call" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:175 +#: lib/block_scout_web/templates/address_contract/index.html.eex:183 +#, elixir-autogen, elixir-format +msgid "Deployed ByteCode" +msgstr "" + +#: 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/_eth_rpc_item.html.eex:150 +#, elixir-autogen, elixir-format +msgid "Description" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Details" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:159 +#, elixir-autogen, elixir-format +msgid "Difficulty" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:145 +#, elixir-autogen, elixir-format +msgid "Displaying the init data provided of the creating transaction." +msgstr "" + +#: lib/block_scout_web/templates/csv_export/index.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Download" +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 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:22 +#, elixir-autogen, elixir-format +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." +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:27 +#, elixir-autogen, elixir-format +msgid "E-mail*" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 +#, elixir-autogen, elixir-format +msgid "EIP-1167" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:215 +#, elixir-autogen, elixir-format +msgid "ERC-1155 " +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:213 +#, elixir-autogen, elixir-format +msgid "ERC-20 " +msgstr "" + +#: 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:214 +#, elixir-autogen, elixir-format +msgid "ERC-721 " +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +#, elixir-autogen, elixir-format +msgid "ETH RPC API Documentation" +msgstr "" + +#: 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_verification_via_multi_part_files/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "EVM Version" +msgstr "" + +#: 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: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 "" + +#: 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 "" + +#: lib/block_scout_web/views/block_view.ex:73 +#, elixir-autogen, elixir-format +msgid "Emission Reward" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:68 +#, elixir-autogen, elixir-format +msgid "Enter the Solidity Contract Code" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Enter the Vyper Contract Code" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Error trying to fetch balances." +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:354 +#, elixir-autogen, elixir-format +msgid "Error: %{reason}" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:352 +#, elixir-autogen, elixir-format +msgid "Error: (Awaiting internal transactions for reason)" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:120 +#, elixir-autogen, elixir-format +msgid "Error: Could not determine contract creator." +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#, elixir-autogen, elixir-format +msgid "Eth RPC" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Expand" +msgstr "" + +#: lib/block_scout_web/templates/csv_export/index.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Export Data" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:212 +#, elixir-autogen, elixir-format +msgid "External libraries" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 +#, elixir-autogen, elixir-format +msgid "Failed to decode input data." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Fast" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:248 +#, elixir-autogen, elixir-format +msgid "Fetching gas used..." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 +#, elixir-autogen, elixir-format +msgid "Fetching holders..." +msgstr "" + +#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Fetching tokens..." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:195 +#: lib/block_scout_web/templates/address/overview.html.eex:203 +#, elixir-autogen, elixir-format +msgid "Fetching transactions..." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:222 +#: lib/block_scout_web/templates/address/overview.html.eex:230 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 +#, elixir-autogen, elixir-format +msgid "Fetching transfers..." +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 "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Forked Blocks (Reorgs)" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:43 +#, elixir-autogen, elixir-format +msgid "Forum" +msgstr "" + +#: 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: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 "" + +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 +#, elixir-autogen, elixir-format +msgid "GET" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Gas Limit" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:353 +#, elixir-autogen, elixir-format +msgid "Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:241 +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#, elixir-autogen, elixir-format +msgid "Gas Used by Transaction" +msgstr "" + +#: 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:240 +#, elixir-autogen, elixir-format +msgid "Gas used by the address." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:60 +#, elixir-autogen, elixir-format +msgid "Genesis Block" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Github" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Go to" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:104 +#, elixir-autogen, elixir-format +msgid "GraphQL" +msgstr "" + +#: 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:22 +#: lib/block_scout_web/views/wei_helpers.ex:77 +#, elixir-autogen, elixir-format +msgid "Gwei" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:123 +#, elixir-autogen, elixir-format +msgid "Hash" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 +#, elixir-autogen, elixir-format +msgid "Holders" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +#, elixir-autogen, elixir-format +msgid "However, in general, the" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 +#, elixir-autogen, elixir-format +msgid "IN" +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:26 +#, elixir-autogen, elixir-format +msgid "If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:52 +#: 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 "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:12 +#, elixir-autogen, elixir-format +msgid "If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:133 +#, elixir-autogen, elixir-format +msgid "Implementation" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:132 +#, elixir-autogen, elixir-format +msgid "Implementation address of the proxy contract." +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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:249 +#, elixir-autogen, elixir-format +msgid "Index position(s) of referenced stale blocks." +msgstr "" + +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Indexed?" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Input" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:219 +#, elixir-autogen, elixir-format +msgid "Interacted With (To)" +msgstr "" + +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Internal Transaction" +msgstr "" + +#: 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:364 +#: lib/block_scout_web/views/transaction_view.ex:513 +#, elixir-autogen, elixir-format +msgid "Internal Transactions" +msgstr "" + +#: lib/block_scout_web/templates/transaction/invalid.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Invalid Transaction Hash" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:16 +#, elixir-autogen, elixir-format +msgid "It could still be in the TX Pool of a different node, waiting to be broadcasted." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:259 +#, elixir-autogen, elixir-format +msgid "Last Balance Update" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/app.html.eex:45 +#, elixir-autogen, elixir-format +msgid "Less than" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 +#, elixir-autogen, elixir-format +msgid "License Expires" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 +#, elixir-autogen, elixir-format +msgid "License ID" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:289 +#, elixir-autogen, elixir-format +msgid "List of token burnt in the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:272 +#, elixir-autogen, elixir-format +msgid "List of token minted in the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:256 +#, elixir-autogen, elixir-format +msgid "List of token transferred in the transaction." +msgstr "" + +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Loading chart..." +msgstr "" + +#: 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:105 +#: 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: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 "" + +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Log Data" +msgstr "" + +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:131 +#, elixir-autogen, elixir-format +msgid "Log Index" +msgstr "" + +#: 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:375 +#: lib/block_scout_web/views/transaction_view.ex:514 +#, elixir-autogen, elixir-format +msgid "Logs" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:52 +#, elixir-autogen, elixir-format +msgid "Main Networks" +msgstr "" + +#: 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:84 +#: lib/block_scout_web/views/address_view.ex:145 +#, 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:392 +#, elixir-autogen, elixir-format +msgid "Max Priority Fee per Gas" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:320 +#, elixir-autogen, elixir-format +msgid "Max of" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:372 +#, elixir-autogen, elixir-format +msgid "Maximum gas amount approved for the transaction." +msgstr "" + +#: 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 "" + +#: 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:198 +#, elixir-autogen, elixir-format +msgid "Metadata" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Method Id" +msgstr "" + +#: 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/transaction_state/_state_change.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Miner" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Minimal Proxy Contract for" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 +#, elixir-autogen, elixir-format +msgid "Model" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 +#, elixir-autogen, elixir-format +msgid "Module" +msgstr "" + +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "More internal transactions have come in" +msgstr "" + +#: 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/transaction/index.html.eex:19 +#, elixir-autogen, elixir-format +msgid "More transactions have come in" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:18 +#, 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/block/overview.html.eex:116 +#, elixir-autogen, elixir-format +msgid "N/A bytes" +msgstr "" + +#: 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/transaction/_decoded_input_body.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Name" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 +#, elixir-autogen, elixir-format +msgid "New Vyper Smart Contract Verification" +msgstr "" + +#: 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 "" + +#: 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_via_multi_part_files/new.html.eex:38 +#, elixir-autogen, elixir-format +msgid "No" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Not unique Token" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#, elixir-autogen, elixir-format +msgid "Number of accounts holding the token" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:275 +#, elixir-autogen, elixir-format +msgid "Number of blocks validated by this validator." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/overview.html.eex:186 +#, elixir-autogen, elixir-format +msgid "Number of transactions related to this address." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 +#, elixir-autogen, elixir-format +msgid "Number of transfers for the token" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:213 +#, elixir-autogen, elixir-format +msgid "Number of transfers to/from this address." +msgstr "" + +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 +#, elixir-autogen, elixir-format +msgid "OUT" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Only the first" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:61 +#, elixir-autogen, elixir-format +msgid "Optimization enabled" +msgstr "" + +#: 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_verification_via_multi_part_files/new.html.eex:54 +#, elixir-autogen, elixir-format +msgid "Optimization runs" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:76 +#, elixir-autogen, elixir-format +msgid "Other Explorers" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:73 +#, 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 "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Page" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:139 +#, elixir-autogen, elixir-format +msgid "Parent Hash" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:62 +#: lib/block_scout_web/views/transaction_view.ex:349 +#: lib/block_scout_web/views/transaction_view.ex:387 +#, elixir-autogen, elixir-format +msgid "Pending" +msgstr "" + +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Pending Transactions" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:254 +#, elixir-autogen, elixir-format +msgid "Position %{index}" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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:95 +#, elixir-autogen, elixir-format +msgid "Price" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:94 +#, elixir-autogen, elixir-format +msgid "Price per token on the exchanges" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/_tile.html.eex:62 +#, elixir-autogen, elixir-format +msgid "Priority Fees" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "QR Code" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#, elixir-autogen, elixir-format +msgid "RPC" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:447 +#, elixir-autogen, elixir-format +msgid "Raw Input" +msgstr "" + +#: 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:515 +#, elixir-autogen, elixir-format +msgid "Raw Trace" +msgstr "" + +#: 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:369 +#: lib/block_scout_web/views/tokens/overview_view.ex:41 +#, elixir-autogen, elixir-format +msgid "Read Contract" +msgstr "" + +#: 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:370 +#, elixir-autogen, elixir-format +msgid "Read Proxy" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Records" +msgstr "" + +#: 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 "" + +#: 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:108 +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:98 +#, elixir-autogen, elixir-format +msgid "Result" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:135 +#, elixir-autogen, elixir-format +msgid "Revert reason" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Reward" +msgstr "" + +#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Run" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/search/results.html.eex:17 +#, elixir-autogen, elixir-format +msgid "Search Results" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 +#, elixir-autogen, elixir-format +msgid "Search tokens" +msgstr "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:27 +#, elixir-autogen, elixir-format +msgid "Self-Destruct" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Show" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Show QR Code" +msgstr "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format +msgid "Shows the current" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:45 +#, elixir-autogen, elixir-format +msgid "Shows total assets held in the address" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:113 +#, elixir-autogen, elixir-format +msgid "Size of the block in bytes." +msgstr "" + +#: 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/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: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_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 "" + +#: lib/block_scout_web/templates/chain/show.html.eex:222 +#, elixir-autogen, elixir-format +msgid "Something went wrong, click to retry." +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Sorry, We are unable to locate this transaction Hash" +msgstr "" + +#: 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_json/new.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Sources and Metadata JSON" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:130 +#, elixir-autogen, elixir-format +msgid "Stakes" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:24 +#, elixir-autogen, elixir-format +msgid "Static Call" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:110 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 +#: lib/block_scout_web/views/transaction_view.ex:351 +#, elixir-autogen, elixir-format +msgid "Success" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 +#, elixir-autogen, elixir-format +msgid "TX Fee" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Telegram" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:65 +#, elixir-autogen, elixir-format +msgid "Test Networks" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:11 +#, 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:30 +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:122 +#, elixir-autogen, elixir-format +msgid "The SHA256 hash of the block." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/overview.html.eex:85 +#, elixir-autogen, elixir-format +msgid "The name of the validator." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:79 +#, elixir-autogen, elixir-format +msgid "The number of transactions in the block." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "The requested path was not found on BlockScout." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:134 +#, elixir-autogen, elixir-format +msgid "The revert reason of the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:109 +#, elixir-autogen, elixir-format +msgid "The status of the transaction: Confirmed or Unconfirmed." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 +#, elixir-autogen, elixir-format +msgid "The total amount of tokens issued" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "There are no blocks." +msgstr "" + +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 +#, elixir-autogen, elixir-format +msgid "There are no holders for this Token." +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_logs/index.html.eex:28 +#, elixir-autogen, elixir-format +msgid "There are no logs for this address." +msgstr "" + +#: lib/block_scout_web/templates/transaction_log/index.html.eex:20 +#, elixir-autogen, elixir-format +msgid "There are no logs for this transaction." +msgstr "" + +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 +#, elixir-autogen, elixir-format +msgid "There are no pending transactions." +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/index.html.eex:65 +#, elixir-autogen, elixir-format +msgid "There are no tokens for this address." +msgstr "" + +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 +#, elixir-autogen, elixir-format +msgid "There are no tokens." +msgstr "" + +#: lib/block_scout_web/templates/address_transaction/index.html.eex:55 +#, elixir-autogen, elixir-format +msgid "There are no transactions for this address." +msgstr "" + +#: lib/block_scout_web/templates/block_transaction/index.html.eex:27 +#, elixir-autogen, elixir-format +msgid "There are no transactions for this block." +msgstr "" + +#: lib/block_scout_web/templates/transaction/index.html.eex:31 +#, elixir-autogen, elixir-format +msgid "There are no transactions." +msgstr "" + +#: 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:27 +#, elixir-autogen, elixir-format +msgid "There are no transfers for this Token." +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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/views/block_transaction_view.ex:11 +#, elixir-autogen, elixir-format +msgid "This block has not been processed yet." +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:41 +#, elixir-autogen, elixir-format +msgid "This contract has been partially verified via Sourcify." +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:45 +#, elixir-autogen, elixir-format +msgid "This contract has been verified via Sourcify." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:64 +#, elixir-autogen, elixir-format +msgid "This transaction is pending confirmation." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:71 +#: lib/block_scout_web/templates/transaction/overview.html.eex:177 +#, elixir-autogen, elixir-format +msgid "Timestamp" +msgstr "" + +#: 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: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 "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "To see accurate decoded input data, the contract must be verified." +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Toggle navigation" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Token" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 +#: lib/block_scout_web/views/transaction_view.ex:449 +#, elixir-autogen, elixir-format +msgid "Token Burning" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 +#: lib/block_scout_web/views/transaction_view.ex:450 +#, elixir-autogen, elixir-format +msgid "Token Creation" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Token ID" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 +#: lib/block_scout_web/views/transaction_view.ex:448 +#, elixir-autogen, elixir-format +msgid "Token Minting" +msgstr "" + +#: 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:451 +#, elixir-autogen, elixir-format +msgid "Token Transfer" +msgstr "" + +#: 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: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:366 +#: 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:512 +#, elixir-autogen, elixir-format +msgid "Token Transfers" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:54 +#, elixir-autogen, elixir-format +msgid "Token name and symbol." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 +#, elixir-autogen, elixir-format +msgid "Token type" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:21 +#: lib/block_scout_web/templates/address/overview.html.eex:176 +#: 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:363 +#, elixir-autogen, elixir-format +msgid "Tokens" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:290 +#, elixir-autogen, elixir-format +msgid "Tokens Burnt" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:306 +#, elixir-autogen, elixir-format +msgid "Tokens Created" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:273 +#, elixir-autogen, elixir-format +msgid "Tokens Minted" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:257 +#, elixir-autogen, elixir-format +msgid "Tokens Transferred" +msgstr "" + +#: lib/block_scout_web/templates/address/_metatags.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Top Accounts - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/address_logs/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Topic" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:169 +#, elixir-autogen, elixir-format +msgid "Total Difficulty" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 +#, elixir-autogen, elixir-format +msgid "Total Supply * Price" +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:130 +#, elixir-autogen, elixir-format +msgid "Total blocks" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:168 +#, elixir-autogen, elixir-format +msgid "Total difficulty of the chain until this block." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:69 +#, elixir-autogen, elixir-format +msgid "Total supply" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:337 +#, elixir-autogen, elixir-format +msgid "Total transaction fee." +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:109 +#, elixir-autogen, elixir-format +msgid "Total transactions" +msgstr "" + +#: 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:461 +#, elixir-autogen, elixir-format +msgid "Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Transaction %{transaction} - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:412 +#, elixir-autogen, elixir-format +msgid "Transaction Burnt Fee" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:50 +#, elixir-autogen, elixir-format +msgid "Transaction Details" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:338 +#, elixir-autogen, elixir-format +msgid "Transaction Fee" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:80 +#, elixir-autogen, elixir-format +msgid "Transaction Hash" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:361 +#, elixir-autogen, elixir-format +msgid "Transaction type, introduced in EIP-2718." +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:7 +#: lib/block_scout_web/templates/address/overview.html.eex:187 +#: lib/block_scout_web/templates/address/overview.html.eex:193 +#: lib/block_scout_web/templates/address/overview.html.eex:201 +#: 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:365 +#, elixir-autogen, elixir-format +msgid "Transactions" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:101 +#, elixir-autogen, elixir-format +msgid "Transactions and address of creation." +msgstr "" + +#: lib/block_scout_web/templates/address/_tile.html.eex:31 +#, elixir-autogen, elixir-format +msgid "Transactions sent" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:214 +#: lib/block_scout_web/templates/address/overview.html.eex:220 +#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:27 +#, elixir-autogen, elixir-format +msgid "Twitter" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:49 +#, elixir-autogen, elixir-format +msgid "Tx/day" +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 "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 +#, elixir-autogen, elixir-format +msgid "Type of the token standard" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:461 +#, elixir-autogen, elixir-format +msgid "UTF-8" +msgstr "" + +#: lib/block_scout_web/views/block_view.ex:77 +#, elixir-autogen, elixir-format +msgid "Uncle Reward" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:250 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Uncles" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:342 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "" + +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Unique Token" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Validated" +msgstr "" + +#: lib/block_scout_web/templates/transaction/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "Validated Transactions" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Validator Creation Date" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Validator Data" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:86 +#, elixir-autogen, elixir-format +msgid "Validator Name" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:323 +#, elixir-autogen, elixir-format +msgid "Value" +msgstr "" + +#: 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 "" + +#: 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/address_contract/index.html.eex:82 +#, elixir-autogen, elixir-format +msgid "Verified at" +msgstr "" + +#: 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:161 +#: lib/block_scout_web/templates/address_contract/index.html.eex:192 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Verify & Publish" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:107 +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Verify the contract " +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 +#, elixir-autogen, elixir-format +msgid "Via Standard Input JSON" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Via flattened source code" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/show.html.eex:212 +#, elixir-autogen, elixir-format +msgid "View All Transactions" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 +#, elixir-autogen, elixir-format +msgid "View Less Transfers" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 +#, elixir-autogen, elixir-format +msgid "View More Transfers" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:39 +#, elixir-autogen, elixir-format +msgid "View next block" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:23 +#, elixir-autogen, elixir-format +msgid "View previous block" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 +#, elixir-autogen, elixir-format +msgid "View transaction %{transaction} on %{subnetwork}" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 +#, elixir-autogen, elixir-format +msgid "Vyper contract" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:136 +#, elixir-autogen, elixir-format +msgid "WEI" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Waiting for transaction's confirmation..." +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:138 +#, elixir-autogen, elixir-format +msgid "Wallet addresses" +msgstr "" + +#: 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 "" + +#: 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:73 +#, 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 "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format +msgid "Write" +msgstr "" + +#: 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:371 +#, elixir-autogen, elixir-format +msgid "Write Contract" +msgstr "" + +#: 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:372 +#, elixir-autogen, elixir-format +msgid "Write Proxy" +msgstr "" + +#: 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_via_multi_part_files/new.html.eex:43 +#, elixir-autogen, elixir-format +msgid "Yes" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format +msgid "balance of the address" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#, elixir-autogen, elixir-format +msgid "button" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:230 +#, elixir-autogen, elixir-format +msgid "created" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +#, elixir-autogen, elixir-format +msgid "custom RPC" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:150 +#, 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 "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:38 +#, elixir-autogen, elixir-format +msgid "fallback" +msgstr "" + +#: lib/block_scout_web/views/address_contract_view.ex:24 +#, elixir-autogen, elixir-format +msgid "false" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "here" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +#, elixir-autogen, elixir-format +msgid "here." +msgstr "" + +#: lib/block_scout_web/templates/transaction/invalid.html.eex:8 +#, elixir-autogen, elixir-format +msgid "is not a valid transaction hash" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 +#, elixir-autogen, elixir-format +msgid "method Response" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format +msgid "of" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 +#, elixir-autogen, elixir-format +msgid "page" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:40 +#, elixir-autogen, elixir-format +msgid "receive" +msgstr "" + +#: 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/_eth_rpc_item.html.eex:70 +#, elixir-autogen, elixir-format +msgid "required" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/address_contract_view.ex:23 +#, elixir-autogen, elixir-format +msgid "true" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:73 +#, elixir-autogen, elixir-format +msgid "truffle flattener" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:5 +#, 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/address_contract_verification_common_fields/_library_first.html.eex:5 +#, elixir-autogen, elixir-format +msgid "A library name called in the .sol file. Multiple libraries (up to " +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Address used in token mintings and burnings." +msgstr "" + +#: 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/transaction_state/_state_change.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Burn address" +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/index.html.eex:38 +#, elixir-autogen, elixir-format +msgid "Change" +msgstr "" + +#: 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:516 +#, elixir-autogen, elixir-format +msgid "State changes" +msgstr "" + +#: 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/transaction_state/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "This transaction hasn't changed state." +msgstr "" + +#: 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/layout/_topnav.html.eex:29 +#, elixir-autogen, elixir-format +msgid "Blockchain" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 +#, elixir-autogen, elixir-format +msgid "Constructor args" +msgstr "" + +#: 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/verified_contracts/_stats.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Contracts" +msgstr "" + +#: 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/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 "" + +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 +#, elixir-autogen, elixir-format +msgid "Market cap" +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/verified_contracts/index.html.eex:69 +#, elixir-autogen, elixir-format +msgid "Optimization" +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/verified_contracts/index.html.eex:93 +#, elixir-autogen, elixir-format +msgid "There are no verified contracts." +msgstr "" + +#: 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/verified_contracts/index.html.eex:60 +#, elixir-autogen, elixir-format +msgid "Txns" +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/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/verified_contracts/_metatags.html.eex:8 +#, elixir-autogen, elixir-format +msgid "View the verified contracts on %{subnetwork}" +msgstr "" + +#: 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/verified_contracts/_metatags.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Verifed contracts, %{subnetwork}, %{coin}" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Blocks With Internal Transactions Indexed" +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 new file mode 100644 index 0000000..ca77f22 --- /dev/null +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -0,0 +1,3426 @@ +#: 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] "" + +#: 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] "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +#, elixir-autogen, elixir-format +msgid " is recommended." +msgstr "" + +#: lib/block_scout_web/templates/address/_metatags.html.eex:3 +#, elixir-autogen, elixir-format +msgid "%{address} - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:12 +#, elixir-autogen, elixir-format +msgid "%{block_type} Details" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:55 +#, elixir-autogen, elixir-format +msgid "%{block_type} Height" +msgstr "" + +#: lib/block_scout_web/templates/block/index.html.eex:7 +#, elixir-autogen, elixir-format +msgid "%{block_type}s" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:85 +#, elixir-autogen, elixir-format +msgid "%{count} Transaction" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "%{subnetwork} %{network} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/layout/_default_title.html.eex:2 +#, elixir-autogen, elixir-format +msgid "%{subnetwork} Explorer - BlockScout" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:350 +#, elixir-autogen, elixir-format +msgid "(Awaiting internal transactions for status)" +msgstr "" + +#: 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:104 +#, elixir-autogen, elixir-format +msgid "(query)" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:230 +#, elixir-autogen, elixir-format +msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:97 +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:23 +#, elixir-autogen, elixir-format +msgid "A block producer who successfully included the block onto the blockchain." +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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/index.html.eex:4 +#, elixir-autogen, elixir-format +msgid "API Documentation" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 +#, elixir-autogen, elixir-format +msgid "API endpoints for the %{subnetwork}" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "API for the %{subnetwork} - BlockScout" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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:82 +#: 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:93 +#: 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/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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:217 +#, elixir-autogen, elixir-format +msgid "Address (external or contract) receiving the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:199 +#, elixir-autogen, elixir-format +msgid "Address (external or contract) sending the transaction." +msgstr "" + +#: 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:150 +#, elixir-autogen, elixir-format +msgid "Address balance in" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 +#, elixir-autogen, elixir-format +msgid "Address of the token contract" +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 "" + +#: 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/_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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#, elixir-autogen, elixir-format +msgid "All metadata displayed below is from that contract. In order to verify current contract, click" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:175 +#, elixir-autogen, elixir-format +msgid "All tokens in the account and total value." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:411 +#, elixir-autogen, elixir-format +msgid "Amount of" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#, elixir-autogen, elixir-format +msgid "Apps" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 +#: lib/block_scout_web/templates/transaction/not_found.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Back Home" +msgstr "" + +#: 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 "" + +#: 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:151 +#: 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 "" + +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Balances" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:207 +#, elixir-autogen, elixir-format +msgid "Base Fee per Gas" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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:158 +#, elixir-autogen, elixir-format +msgid "Block" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Block #%{number}" +msgstr "" + +#: lib/block_scout_web/templates/block/_metatags.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Block %{block_number} - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/block_transaction/404.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Block Details" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:53 +#, elixir-autogen, elixir-format +msgid "Block Height" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:43 +#, elixir-autogen, elixir-format +msgid "Block Mined, awaiting import..." +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:34 +#, elixir-autogen, elixir-format +msgid "Block Pending" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/block_transaction_view.ex:15 +#, elixir-autogen, elixir-format +msgid "Block not found, please try again later." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:157 +#, elixir-autogen, elixir-format +msgid "Block number containing the transaction." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:258 +#, elixir-autogen, elixir-format +msgid "Block number in which the address was updated." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/show.html.eex:153 +#: 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 "" + +#: lib/block_scout_web/templates/layout/app.html.eex:42 +#, elixir-autogen, elixir-format +msgid "Blocks Indexed" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:48 +#: lib/block_scout_web/templates/address/overview.html.eex:276 +#: lib/block_scout_web/templates/address_validation/index.html.eex:11 +#: lib/block_scout_web/views/address_view.ex:374 +#, elixir-autogen, elixir-format +msgid "Blocks Validated" +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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:65 +#, elixir-autogen, elixir-format +msgid "CRC Worth" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2 +#, elixir-autogen, elixir-format +msgid "CSV" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:22 +#, elixir-autogen, elixir-format +msgid "Call Code" +msgstr "" + +#: 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:111 +#: 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/api_docs/_eth_rpc_item.html.eex:54 +#, elixir-autogen, elixir-format +msgid "Cancel" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Chat (#blockscout)" +msgstr "" + +#: lib/block_scout_web/views/block_view.ex:65 +#, elixir-autogen, elixir-format +msgid "Chore Reward" +msgstr "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Close" +msgstr "" + +#: 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:367 +#, elixir-autogen, elixir-format +msgid "Code" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:34 +#: lib/block_scout_web/views/address_view.ex:373 +#, elixir-autogen, elixir-format +msgid "Coin Balance History" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Collapse" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:65 +#, elixir-autogen, elixir-format +msgid "Compiler version" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:343 +#, elixir-autogen, elixir-format +msgid "Confirmed" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:124 +#, elixir-autogen, elixir-format +msgid "Confirmed by " +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:190 +#, elixir-autogen, elixir-format +msgid "Confirmed within" +msgstr "" + +#: 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:16 +#, elixir-autogen, elixir-format +msgid "Connection Lost" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Connection Lost, click to load newer transactions" +msgstr "" + +#: lib/block_scout_web/templates/address_validation/index.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Connection Lost, click to load newer validations" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:90 +#, elixir-autogen, elixir-format +msgid "Constructor Arguments" +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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:122 +#, elixir-autogen, elixir-format +msgid "Contract ABI" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 +#: 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 "" + +#: lib/block_scout_web/views/transaction_view.ex:458 +#, elixir-autogen, elixir-format +msgid "Contract Call" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:455 +#, elixir-autogen, elixir-format +msgid "Contract Creation" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:138 +#: lib/block_scout_web/templates/address_contract/index.html.eex:153 +#, elixir-autogen, elixir-format +msgid "Contract Creation Code" +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:80 +#, elixir-autogen, elixir-format +msgid "Contract Libraries" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:25 +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:57 +#, elixir-autogen, elixir-format +msgid "Contract name:" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:100 +#, elixir-autogen, elixir-format +msgid "Contract source code" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:144 +#, elixir-autogen, elixir-format +msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:40 +#, elixir-autogen, elixir-format +msgid "Contribute" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:124 +#, elixir-autogen, elixir-format +msgid "Copy ABI" +msgstr "" + +#: 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 "" + +#: 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:140 +#: lib/block_scout_web/templates/address_contract/index.html.eex:156 +#, elixir-autogen, elixir-format +msgid "Copy Contract Creation Code" +msgstr "" + +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Copy Decompiled Contract Code" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:177 +#: lib/block_scout_web/templates/address_contract/index.html.eex:187 +#, elixir-autogen, elixir-format +msgid "Copy Deployed ByteCode" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 +#, elixir-autogen, elixir-format +msgid "Copy Metadata" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15 +#, elixir-autogen, elixir-format +msgid "Copy Raw Trace" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:102 +#: lib/block_scout_web/templates/address_contract/index.html.eex:113 +#, elixir-autogen, elixir-format +msgid "Copy Source Code" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:87 +#, elixir-autogen, elixir-format +msgid "Copy Transaction Hash" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:88 +#, elixir-autogen, elixir-format +msgid "Copy Txn Hash" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:472 +#, elixir-autogen, elixir-format +msgid "Copy Txn Hex Input" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:478 +#, elixir-autogen, elixir-format +msgid "Copy Txn UTF-8 Input" +msgstr "" + +#: 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/overview.html.eex:477 +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Copy Value" +msgstr "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:25 +#, elixir-autogen, elixir-format +msgid "Create" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/overview.html.eex:102 +#, elixir-autogen, elixir-format +msgid "Creator" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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/transaction_log/_logs.html.eex:121 +#, elixir-autogen, elixir-format +msgid "Data" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:70 +#, elixir-autogen, elixir-format +msgid "Date & time at which block was produced." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 +#, elixir-autogen, elixir-format +msgid "Decimals" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Decoded" +msgstr "" + +#: lib/block_scout_web/views/address_view.ex:368 +#, elixir-autogen, elixir-format +msgid "Decompiled Code" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:75 +#, elixir-autogen, elixir-format +msgid "Decompiled code" +msgstr "" + +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "Decompiled contract code" +msgstr "" + +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Decompiler version" +msgstr "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:23 +#, elixir-autogen, elixir-format +msgid "Delegate Call" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:175 +#: lib/block_scout_web/templates/address_contract/index.html.eex:183 +#, elixir-autogen, elixir-format +msgid "Deployed ByteCode" +msgstr "" + +#: 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/_eth_rpc_item.html.eex:150 +#, elixir-autogen, elixir-format +msgid "Description" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Details" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:159 +#, elixir-autogen, elixir-format +msgid "Difficulty" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:145 +#, elixir-autogen, elixir-format +msgid "Displaying the init data provided of the creating transaction." +msgstr "" + +#: lib/block_scout_web/templates/csv_export/index.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Download" +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 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:22 +#, elixir-autogen, elixir-format +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." +msgstr "" + +#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:27 +#, elixir-autogen, elixir-format +msgid "E-mail*" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 +#, elixir-autogen, elixir-format +msgid "EIP-1167" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:215 +#, elixir-autogen, elixir-format +msgid "ERC-1155 " +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:213 +#, elixir-autogen, elixir-format +msgid "ERC-20 " +msgstr "" + +#: 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:214 +#, elixir-autogen, elixir-format +msgid "ERC-721 " +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +#, elixir-autogen, elixir-format +msgid "ETH RPC API Documentation" +msgstr "" + +#: 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_verification_via_multi_part_files/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "EVM Version" +msgstr "" + +#: 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: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 "" + +#: 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 "" + +#: lib/block_scout_web/views/block_view.ex:73 +#, elixir-autogen, elixir-format +msgid "Emission Reward" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:68 +#, elixir-autogen, elixir-format +msgid "Enter the Solidity Contract Code" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Enter the Vyper Contract Code" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Error trying to fetch balances." +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:354 +#, elixir-autogen, elixir-format +msgid "Error: %{reason}" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:352 +#, elixir-autogen, elixir-format +msgid "Error: (Awaiting internal transactions for reason)" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:120 +#, elixir-autogen, elixir-format +msgid "Error: Could not determine contract creator." +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#, elixir-autogen, elixir-format +msgid "Eth RPC" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Expand" +msgstr "" + +#: lib/block_scout_web/templates/csv_export/index.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Export Data" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:212 +#, elixir-autogen, elixir-format +msgid "External libraries" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 +#, elixir-autogen, elixir-format +msgid "Failed to decode input data." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Fast" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:248 +#, elixir-autogen, elixir-format +msgid "Fetching gas used..." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 +#, elixir-autogen, elixir-format +msgid "Fetching holders..." +msgstr "" + +#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Fetching tokens..." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:195 +#: lib/block_scout_web/templates/address/overview.html.eex:203 +#, elixir-autogen, elixir-format +msgid "Fetching transactions..." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:222 +#: lib/block_scout_web/templates/address/overview.html.eex:230 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 +#, elixir-autogen, elixir-format +msgid "Fetching transfers..." +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 "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 +#, elixir-autogen, elixir-format +msgid "Forked Blocks (Reorgs)" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:43 +#, elixir-autogen, elixir-format +msgid "Forum" +msgstr "" + +#: 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: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 "" + +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 +#, elixir-autogen, elixir-format +msgid "GET" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Gas Limit" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:353 +#, elixir-autogen, elixir-format +msgid "Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:241 +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#, elixir-autogen, elixir-format +msgid "Gas Used by Transaction" +msgstr "" + +#: 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:240 +#, elixir-autogen, elixir-format +msgid "Gas used by the address." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:60 +#, elixir-autogen, elixir-format +msgid "Genesis Block" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Github" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 +#, elixir-autogen, elixir-format +msgid "Go to" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:104 +#, elixir-autogen, elixir-format +msgid "GraphQL" +msgstr "" + +#: 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:22 +#: lib/block_scout_web/views/wei_helpers.ex:77 +#, elixir-autogen, elixir-format +msgid "Gwei" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:123 +#, elixir-autogen, elixir-format +msgid "Hash" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 +#, elixir-autogen, elixir-format +msgid "Holders" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +#, elixir-autogen, elixir-format +msgid "However, in general, the" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 +#, elixir-autogen, elixir-format +msgid "IN" +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:26 +#, elixir-autogen, elixir-format +msgid "If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:52 +#: 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 "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:12 +#, elixir-autogen, elixir-format +msgid "If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:133 +#, elixir-autogen, elixir-format +msgid "Implementation" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:132 +#, elixir-autogen, elixir-format +msgid "Implementation address of the proxy contract." +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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:249 +#, elixir-autogen, elixir-format +msgid "Index position(s) of referenced stale blocks." +msgstr "" + +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Indexed?" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Input" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:219 +#, elixir-autogen, elixir-format +msgid "Interacted With (To)" +msgstr "" + +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Internal Transaction" +msgstr "" + +#: 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:364 +#: lib/block_scout_web/views/transaction_view.ex:513 +#, elixir-autogen, elixir-format +msgid "Internal Transactions" +msgstr "" + +#: lib/block_scout_web/templates/transaction/invalid.html.eex:6 +#, elixir-autogen, elixir-format +msgid "Invalid Transaction Hash" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:16 +#, elixir-autogen, elixir-format +msgid "It could still be in the TX Pool of a different node, waiting to be broadcasted." +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:259 +#, elixir-autogen, elixir-format +msgid "Last Balance Update" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/app.html.eex:45 +#, elixir-autogen, elixir-format +msgid "Less than" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 +#, elixir-autogen, elixir-format +msgid "License Expires" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 +#, elixir-autogen, elixir-format +msgid "License ID" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:289 +#, elixir-autogen, elixir-format +msgid "List of token burnt in the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:272 +#, elixir-autogen, elixir-format +msgid "List of token minted in the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:256 +#, elixir-autogen, elixir-format +msgid "List of token transferred in the transaction." +msgstr "" + +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Loading chart..." +msgstr "" + +#: 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:105 +#: 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: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 "" + +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Log Data" +msgstr "" + +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:131 +#, elixir-autogen, elixir-format +msgid "Log Index" +msgstr "" + +#: 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:375 +#: lib/block_scout_web/views/transaction_view.ex:514 +#, elixir-autogen, elixir-format +msgid "Logs" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:52 +#, elixir-autogen, elixir-format +msgid "Main Networks" +msgstr "" + +#: 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:84 +#: lib/block_scout_web/views/address_view.ex:145 +#, 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:392 +#, elixir-autogen, elixir-format +msgid "Max Priority Fee per Gas" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:320 +#, elixir-autogen, elixir-format +msgid "Max of" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:372 +#, elixir-autogen, elixir-format +msgid "Maximum gas amount approved for the transaction." +msgstr "" + +#: 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 "" + +#: 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:198 +#, elixir-autogen, elixir-format +msgid "Metadata" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Method Id" +msgstr "" + +#: 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/transaction_state/_state_change.html.eex:24 +#, elixir-autogen, elixir-format +msgid "Miner" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Minimal Proxy Contract for" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 +#, elixir-autogen, elixir-format +msgid "Model" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 +#, elixir-autogen, elixir-format +msgid "Module" +msgstr "" + +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "More internal transactions have come in" +msgstr "" + +#: 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/transaction/index.html.eex:19 +#, elixir-autogen, elixir-format +msgid "More transactions have come in" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:18 +#, 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/block/overview.html.eex:116 +#, elixir-autogen, elixir-format +msgid "N/A bytes" +msgstr "" + +#: 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/transaction/_decoded_input_body.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Name" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 +#, elixir-autogen, elixir-format +msgid "New Vyper Smart Contract Verification" +msgstr "" + +#: 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 "" + +#: 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_via_multi_part_files/new.html.eex:38 +#, elixir-autogen, elixir-format +msgid "No" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Not unique Token" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#, elixir-autogen, elixir-format +msgid "Number of accounts holding the token" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:275 +#, elixir-autogen, elixir-format +msgid "Number of blocks validated by this validator." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/overview.html.eex:186 +#, elixir-autogen, elixir-format +msgid "Number of transactions related to this address." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 +#, elixir-autogen, elixir-format +msgid "Number of transfers for the token" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:213 +#, elixir-autogen, elixir-format +msgid "Number of transfers to/from this address." +msgstr "" + +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 +#, elixir-autogen, elixir-format +msgid "OUT" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Only the first" +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:61 +#, elixir-autogen, elixir-format +msgid "Optimization enabled" +msgstr "" + +#: 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_verification_via_multi_part_files/new.html.eex:54 +#, elixir-autogen, elixir-format +msgid "Optimization runs" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:76 +#, elixir-autogen, elixir-format +msgid "Other Explorers" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:73 +#, 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 "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Page" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:139 +#, elixir-autogen, elixir-format +msgid "Parent Hash" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:62 +#: lib/block_scout_web/views/transaction_view.ex:349 +#: lib/block_scout_web/views/transaction_view.ex:387 +#, elixir-autogen, elixir-format +msgid "Pending" +msgstr "" + +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Pending Transactions" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:254 +#, elixir-autogen, elixir-format +msgid "Position %{index}" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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:95 +#, elixir-autogen, elixir-format +msgid "Price" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:94 +#, elixir-autogen, elixir-format +msgid "Price per token on the exchanges" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/_tile.html.eex:62 +#, elixir-autogen, elixir-format +msgid "Priority Fees" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "QR Code" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format +msgid "Query" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#, elixir-autogen, elixir-format +msgid "RPC" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:447 +#, elixir-autogen, elixir-format +msgid "Raw Input" +msgstr "" + +#: 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:515 +#, elixir-autogen, elixir-format +msgid "Raw Trace" +msgstr "" + +#: 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:369 +#: lib/block_scout_web/views/tokens/overview_view.ex:41 +#, elixir-autogen, elixir-format +msgid "Read Contract" +msgstr "" + +#: 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:370 +#, elixir-autogen, elixir-format +msgid "Read Proxy" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Records" +msgstr "" + +#: 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 "" + +#: 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:108 +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:98 +#, elixir-autogen, elixir-format +msgid "Result" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:135 +#, elixir-autogen, elixir-format +msgid "Revert reason" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Reward" +msgstr "" + +#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 +#, elixir-autogen, elixir-format +msgid "Run" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/search/results.html.eex:17 +#, elixir-autogen, elixir-format +msgid "Search Results" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 +#, elixir-autogen, elixir-format +msgid "Search tokens" +msgstr "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:27 +#, elixir-autogen, elixir-format +msgid "Self-Destruct" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Show" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Show QR Code" +msgstr "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format +msgid "Shows the current" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:45 +#, elixir-autogen, elixir-format +msgid "Shows total assets held in the address" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:113 +#, elixir-autogen, elixir-format +msgid "Size of the block in bytes." +msgstr "" + +#: 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/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: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_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 "" + +#: lib/block_scout_web/templates/chain/show.html.eex:222 +#, elixir-autogen, elixir-format +msgid "Something went wrong, click to retry." +msgstr "" + +#: lib/block_scout_web/templates/transaction/not_found.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Sorry, We are unable to locate this transaction Hash" +msgstr "" + +#: 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_json/new.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Sources and Metadata JSON" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:130 +#, elixir-autogen, elixir-format +msgid "Stakes" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/internal_transaction_view.ex:24 +#, elixir-autogen, elixir-format +msgid "Static Call" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:110 +#, elixir-autogen, elixir-format +msgid "Status" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 +#: lib/block_scout_web/views/transaction_view.ex:351 +#, elixir-autogen, elixir-format +msgid "Success" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 +#, elixir-autogen, elixir-format +msgid "TX Fee" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Telegram" +msgstr "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:65 +#, elixir-autogen, elixir-format +msgid "Test Networks" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:11 +#, 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:30 +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:122 +#, elixir-autogen, elixir-format +msgid "The SHA256 hash of the block." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address/overview.html.eex:85 +#, elixir-autogen, elixir-format +msgid "The name of the validator." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:79 +#, elixir-autogen, elixir-format +msgid "The number of transactions in the block." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 +#, elixir-autogen, elixir-format +msgid "The requested path was not found on BlockScout." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:134 +#, elixir-autogen, elixir-format +msgid "The revert reason of the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:109 +#, elixir-autogen, elixir-format +msgid "The status of the transaction: Confirmed or Unconfirmed." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 +#, elixir-autogen, elixir-format +msgid "The total amount of tokens issued" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "There are no blocks." +msgstr "" + +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 +#, elixir-autogen, elixir-format +msgid "There are no holders for this Token." +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_logs/index.html.eex:28 +#, elixir-autogen, elixir-format +msgid "There are no logs for this address." +msgstr "" + +#: lib/block_scout_web/templates/transaction_log/index.html.eex:20 +#, elixir-autogen, elixir-format +msgid "There are no logs for this transaction." +msgstr "" + +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 +#, elixir-autogen, elixir-format +msgid "There are no pending transactions." +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/index.html.eex:65 +#, elixir-autogen, elixir-format +msgid "There are no tokens for this address." +msgstr "" + +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 +#, elixir-autogen, elixir-format +msgid "There are no tokens." +msgstr "" + +#: lib/block_scout_web/templates/address_transaction/index.html.eex:55 +#, elixir-autogen, elixir-format +msgid "There are no transactions for this address." +msgstr "" + +#: lib/block_scout_web/templates/block_transaction/index.html.eex:27 +#, elixir-autogen, elixir-format +msgid "There are no transactions for this block." +msgstr "" + +#: lib/block_scout_web/templates/transaction/index.html.eex:31 +#, elixir-autogen, elixir-format +msgid "There are no transactions." +msgstr "" + +#: 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:27 +#, elixir-autogen, elixir-format +msgid "There are no transfers for this Token." +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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/views/block_transaction_view.ex:11 +#, elixir-autogen, elixir-format +msgid "This block has not been processed yet." +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:41 +#, elixir-autogen, elixir-format +msgid "This contract has been partially verified via Sourcify." +msgstr "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:45 +#, elixir-autogen, elixir-format +msgid "This contract has been verified via Sourcify." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:64 +#, elixir-autogen, elixir-format +msgid "This transaction is pending confirmation." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:71 +#: lib/block_scout_web/templates/transaction/overview.html.eex:177 +#, elixir-autogen, elixir-format +msgid "Timestamp" +msgstr "" + +#: 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: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 "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "To see accurate decoded input data, the contract must be verified." +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 +#, elixir-autogen, elixir-format +msgid "Toggle navigation" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Token" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 +#: lib/block_scout_web/views/transaction_view.ex:449 +#, elixir-autogen, elixir-format +msgid "Token Burning" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 +#: lib/block_scout_web/views/transaction_view.ex:450 +#, elixir-autogen, elixir-format +msgid "Token Creation" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Token ID" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 +#: lib/block_scout_web/views/transaction_view.ex:448 +#, elixir-autogen, elixir-format +msgid "Token Minting" +msgstr "" + +#: 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:451 +#, elixir-autogen, elixir-format +msgid "Token Transfer" +msgstr "" + +#: 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: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:366 +#: 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:512 +#, elixir-autogen, elixir-format +msgid "Token Transfers" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:54 +#, elixir-autogen, elixir-format +msgid "Token name and symbol." +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 +#, elixir-autogen, elixir-format +msgid "Token type" +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:21 +#: lib/block_scout_web/templates/address/overview.html.eex:176 +#: 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:363 +#, elixir-autogen, elixir-format +msgid "Tokens" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:290 +#, elixir-autogen, elixir-format +msgid "Tokens Burnt" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:306 +#, elixir-autogen, elixir-format +msgid "Tokens Created" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:273 +#, elixir-autogen, elixir-format +msgid "Tokens Minted" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:257 +#, elixir-autogen, elixir-format +msgid "Tokens Transferred" +msgstr "" + +#: lib/block_scout_web/templates/address/_metatags.html.eex:13 +#, elixir-autogen, elixir-format +msgid "Top Accounts - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/address_logs/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Topic" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/block/overview.html.eex:169 +#, elixir-autogen, elixir-format +msgid "Total Difficulty" +msgstr "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 +#, elixir-autogen, elixir-format +msgid "Total Supply * Price" +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:130 +#, elixir-autogen, elixir-format +msgid "Total blocks" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:168 +#, elixir-autogen, elixir-format +msgid "Total difficulty of the chain until this block." +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:69 +#, elixir-autogen, elixir-format +msgid "Total supply" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:337 +#, elixir-autogen, elixir-format +msgid "Total transaction fee." +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:109 +#, elixir-autogen, elixir-format +msgid "Total transactions" +msgstr "" + +#: 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:461 +#, elixir-autogen, elixir-format +msgid "Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Transaction %{transaction} - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 +#, elixir-autogen, elixir-format +msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:412 +#, elixir-autogen, elixir-format +msgid "Transaction Burnt Fee" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:50 +#, elixir-autogen, elixir-format +msgid "Transaction Details" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:338 +#, elixir-autogen, elixir-format +msgid "Transaction Fee" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:80 +#, elixir-autogen, elixir-format +msgid "Transaction Hash" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:361 +#, elixir-autogen, elixir-format +msgid "Transaction type, introduced in EIP-2718." +msgstr "" + +#: lib/block_scout_web/templates/address/_tabs.html.eex:7 +#: lib/block_scout_web/templates/address/overview.html.eex:187 +#: lib/block_scout_web/templates/address/overview.html.eex:193 +#: lib/block_scout_web/templates/address/overview.html.eex:201 +#: 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:365 +#, elixir-autogen, elixir-format +msgid "Transactions" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:101 +#, elixir-autogen, elixir-format +msgid "Transactions and address of creation." +msgstr "" + +#: lib/block_scout_web/templates/address/_tile.html.eex:31 +#, elixir-autogen, elixir-format +msgid "Transactions sent" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:214 +#: lib/block_scout_web/templates/address/overview.html.eex:220 +#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/_footer.html.eex:27 +#, elixir-autogen, elixir-format +msgid "Twitter" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:49 +#, elixir-autogen, elixir-format +msgid "Tx/day" +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 "" + +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 +#, elixir-autogen, elixir-format +msgid "Type of the token standard" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:461 +#, elixir-autogen, elixir-format +msgid "UTF-8" +msgstr "" + +#: lib/block_scout_web/views/block_view.ex:77 +#, elixir-autogen, elixir-format +msgid "Uncle Reward" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:250 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 +#, elixir-autogen, elixir-format +msgid "Uncles" +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:342 +#, elixir-autogen, elixir-format +msgid "Unconfirmed" +msgstr "" + +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Unique Token" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#, elixir-autogen, elixir-format +msgid "Validated" +msgstr "" + +#: lib/block_scout_web/templates/transaction/index.html.eex:12 +#, elixir-autogen, elixir-format +msgid "Validated Transactions" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 +#, elixir-autogen, elixir-format +msgid "Validator Creation Date" +msgstr "" + +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 +#, elixir-autogen, elixir-format +msgid "Validator Data" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:86 +#, elixir-autogen, elixir-format +msgid "Validator Name" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:323 +#, elixir-autogen, elixir-format +msgid "Value" +msgstr "" + +#: 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 "" + +#: 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/address_contract/index.html.eex:82 +#, elixir-autogen, elixir-format +msgid "Verified at" +msgstr "" + +#: 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:161 +#: lib/block_scout_web/templates/address_contract/index.html.eex:192 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Verify & Publish" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:107 +#: 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 "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "Verify the contract " +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 +#, elixir-autogen, elixir-format +msgid "Via Standard Input JSON" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 +#, elixir-autogen, elixir-format +msgid "Via flattened source code" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/chain/show.html.eex:212 +#, elixir-autogen, elixir-format +msgid "View All Transactions" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 +#, elixir-autogen, elixir-format +msgid "View Less Transfers" +msgstr "" + +#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 +#, elixir-autogen, elixir-format +msgid "View More Transfers" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:39 +#, elixir-autogen, elixir-format +msgid "View next block" +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:23 +#, elixir-autogen, elixir-format +msgid "View previous block" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 +#, elixir-autogen, elixir-format +msgid "View transaction %{transaction} on %{subnetwork}" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 +#, elixir-autogen, elixir-format +msgid "Vyper contract" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:136 +#, elixir-autogen, elixir-format +msgid "WEI" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Waiting for transaction's confirmation..." +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:138 +#, elixir-autogen, elixir-format +msgid "Wallet addresses" +msgstr "" + +#: 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 "" + +#: 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:73 +#, 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 "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:100 +#, elixir-autogen, elixir-format +msgid "Write" +msgstr "" + +#: 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:371 +#, elixir-autogen, elixir-format +msgid "Write Contract" +msgstr "" + +#: 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:372 +#, elixir-autogen, elixir-format +msgid "Write Proxy" +msgstr "" + +#: 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_via_multi_part_files/new.html.eex:43 +#, elixir-autogen, elixir-format +msgid "Yes" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_token/overview.html.eex:52 +#, elixir-autogen, elixir-format +msgid "balance of the address" +msgstr "" + +#: 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 "" + +#: 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 "" + +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#, elixir-autogen, elixir-format +msgid "button" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:230 +#, elixir-autogen, elixir-format +msgid "created" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +#, elixir-autogen, elixir-format +msgid "custom RPC" +msgstr "" + +#: lib/block_scout_web/templates/address/overview.html.eex:150 +#, 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 "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:38 +#, elixir-autogen, elixir-format +msgid "fallback" +msgstr "" + +#: lib/block_scout_web/views/address_contract_view.ex:24 +#, elixir-autogen, elixir-format +msgid "false" +msgstr "" + +#: 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 +#, elixir-autogen, elixir-format +msgid "here" +msgstr "" + +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +#, elixir-autogen, elixir-format +msgid "here." +msgstr "" + +#: lib/block_scout_web/templates/transaction/invalid.html.eex:8 +#, elixir-autogen, elixir-format +msgid "is not a valid transaction hash" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 +#, elixir-autogen, elixir-format +msgid "method Response" +msgstr "" + +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 +#, elixir-autogen, elixir-format +msgid "of" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 +#, elixir-autogen, elixir-format +msgid "page" +msgstr "" + +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:40 +#, elixir-autogen, elixir-format +msgid "receive" +msgstr "" + +#: 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/_eth_rpc_item.html.eex:70 +#, elixir-autogen, elixir-format +msgid "required" +msgstr "" + +#: 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 "" + +#: lib/block_scout_web/views/address_contract_view.ex:23 +#, elixir-autogen, elixir-format +msgid "true" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:73 +#, elixir-autogen, elixir-format +msgid "truffle flattener" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:5 +#, 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/address_contract_verification_common_fields/_library_first.html.eex:5 +#, elixir-autogen, elixir-format, fuzzy +msgid "A library name called in the .sol file. Multiple libraries (up to " +msgstr "" + +#: 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, fuzzy +msgid "Library" +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:9 +#, elixir-autogen, elixir-format +msgid "Address used in token mintings and burnings." +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/index.html.eex:35 +#, elixir-autogen, elixir-format, fuzzy +msgid "Balance after" +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/index.html.eex:32 +#, elixir-autogen, elixir-format, fuzzy +msgid "Balance before" +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:10 +#, elixir-autogen, elixir-format +msgid "Burn address" +msgstr "" + +#: lib/block_scout_web/templates/transaction_state/index.html.eex:38 +#, elixir-autogen, elixir-format +msgid "Change" +msgstr "" + +#: 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:516 +#, elixir-autogen, elixir-format +msgid "State changes" +msgstr "" + +#: 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/transaction_state/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "This transaction hasn't changed state." +msgstr "" + +#: 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/layout/_topnav.html.eex:29 +#, elixir-autogen, elixir-format, fuzzy +msgid "Blockchain" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 +#, elixir-autogen, elixir-format, fuzzy +msgid "Constructor args" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:41 +#, elixir-autogen, elixir-format, fuzzy +msgid "Contract name or address" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 +#, elixir-autogen, elixir-format, fuzzy +msgid "Contracts" +msgstr "" + +#: 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/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 "" + +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 +#, elixir-autogen, elixir-format, fuzzy +msgid "Market cap" +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/verified_contracts/index.html.eex:69 +#, elixir-autogen, elixir-format, fuzzy +msgid "Optimization" +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/verified_contracts/index.html.eex:93 +#, elixir-autogen, elixir-format, fuzzy +msgid "There are no verified contracts." +msgstr "" + +#: 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, fuzzy +msgid "Total" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 +#, elixir-autogen, elixir-format +msgid "Txns" +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, fuzzy +msgid "Verified Contracts" +msgstr "" + +#: lib/block_scout_web/templates/layout/_topnav.html.eex:68 +#, elixir-autogen, elixir-format, fuzzy +msgid "Verified contracts" +msgstr "" + +#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 +#, elixir-autogen, elixir-format, fuzzy +msgid "Verified contracts - %{subnetwork} Explorer" +msgstr "" + +#: 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/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/verified_contracts/_metatags.html.eex:7 +#, elixir-autogen, elixir-format, fuzzy +msgid "Verifed contracts, %{subnetwork}, %{coin}" +msgstr "" + +#: lib/block_scout_web/templates/layout/app.html.eex:44 +#, elixir-autogen, elixir-format, fuzzy +msgid "Blocks With Internal Transactions Indexed" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/errors.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 0000000..cdaaac6 --- /dev/null +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,94 @@ +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "" + +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "" + +msgid "must be less than or equal to %{number}" +msgstr "" + +msgid "must be greater than or equal to %{number}" +msgstr "" + +msgid "must be equal to %{number}" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/errors.pot b/apps/block_scout_web/priv/gettext/errors.pot new file mode 100644 index 0000000..cdaaac6 --- /dev/null +++ b/apps/block_scout_web/priv/gettext/errors.pot @@ -0,0 +1,94 @@ +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "" + +## From Ecto.Changeset.validate_acceptance/3 +msgid "must be accepted" +msgstr "" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "" + +msgid "must be less than or equal to %{number}" +msgstr "" + +msgid "must be greater than or equal to %{number}" +msgstr "" + +msgid "must be equal to %{number}" +msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/chain_test.exs b/apps/block_scout_web/test/block_scout_web/chain_test.exs new file mode 100644 index 0000000..7656ff2 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/chain_test.exs @@ -0,0 +1,82 @@ +defmodule BlockScoutWeb.ChainTest do + use Explorer.DataCase + + alias Explorer.Chain.{Address, Block, Transaction} + alias BlockScoutWeb.Chain + + describe "current_filter/1" do + test "sets direction based on to filter" do + assert [direction: :to] = Chain.current_filter(%{"filter" => "to"}) + end + + test "sets direction based on from filter" do + assert [direction: :from] = Chain.current_filter(%{"filter" => "from"}) + end + + test "no direction set" do + assert [] = Chain.current_filter(%{}) + end + + test "no direction set with paging_options" do + assert [paging_options: "test"] = Chain.current_filter(%{paging_options: "test"}) + end + end + + describe "from_param/1" do + test "finds a block by block number with a valid block number" do + %Block{number: number} = insert(:block, number: 37) + + assert {:ok, %Block{number: ^number}} = + number + |> to_string() + |> Chain.from_param() + end + + test "finds a transaction by hash string" do + transaction = %Transaction{hash: hash} = insert(:transaction) + + assert {:ok, %Transaction{hash: ^hash}} = transaction |> Phoenix.Param.to_param() |> Chain.from_param() + end + + test "finds an address by hash string" do + address = %Address{hash: hash} = insert(:address) + + assert {:ok, %Address{hash: ^hash}} = address |> Phoenix.Param.to_param() |> Chain.from_param() + end + + test "finds a token by its name" do + name = "AYR" + insert(:token, symbol: name) + + assert {:ok, %Address{}} = name |> Chain.from_param() + end + + test "finds a token by its name even if lowercase name was passed" do + name = "ayr" + insert(:token, symbol: String.upcase(name)) + + assert {:ok, %Address{}} = name |> Chain.from_param() + end + + test "returns {:error, :not_found} when garbage is passed in" do + assert {:error, :not_found} = Chain.from_param("any ol' thing") + end + + test "returns {:error, :not_found} when it does not find a match" do + transaction_hash = String.pad_trailing("0xnonsense", 43, "0") + address_hash = String.pad_trailing("0xbaddress", 42, "0") + + assert {:error, :not_found} = Chain.from_param("38999") + assert {:error, :not_found} = Chain.from_param(transaction_hash) + assert {:error, :not_found} = Chain.from_param(address_hash) + end + end + + describe "Posion.encode!" do + test "correctly encodes decimal values" do + val = Decimal.from_float(5.55) + + assert "\"5.55\"" == Poison.encode!(val) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs new file mode 100644 index 0000000..07b890f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs @@ -0,0 +1,231 @@ +defmodule BlockScoutWeb.AddressChannelTest do + use BlockScoutWeb.ChannelCase, + # ETS tables are shared in `Explorer.Counters.AddressesCounter` + async: false + + alias BlockScoutWeb.UserSocket + alias BlockScoutWeb.Notifier + alias Explorer.Counters.AddressesCounter + + test "subscribed user is notified of new_address count event" do + topic = "addresses:new_address" + @endpoint.subscribe(topic) + + address = insert(:address) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + Notifier.handle_event({:chain_event, :addresses, :realtime, [address]}) + + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "count", payload: %{count: _}}, :timer.seconds(5) + end + + describe "user pushing to channel" do + setup do + address = insert(:address, fetched_coin_balance: 100_000, fetched_coin_balance_block_number: 1) + topic = "addresses:#{address.hash}" + + {:ok, _, socket} = + UserSocket + |> socket("no_id", %{locale: "en"}) + |> subscribe_and_join(topic) + + {:ok, %{address: address, topic: topic, socket: socket}} + end + + test "can retrieve current balance card of the address", %{socket: socket, address: address} do + ref = push(socket, "get_balance", %{}) + + assert_reply(ref, :ok, %{balance: sent_balance, balance_card: _balance_card}) + + assert sent_balance == address.fetched_coin_balance.value + # assert balance_card =~ "/address/#{address.hash}/token-balances" + end + end + + describe "user subscribed to address" do + setup do + address = insert(:address) + topic = "addresses:#{address.hash}" + @endpoint.subscribe(topic) + {:ok, %{address: address, topic: topic}} + end + + test "notified of balance_update for matching address", %{address: address, topic: topic} do + address_with_balance = %{address | fetched_coin_balance: 1} + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + Notifier.handle_event({:chain_event, :addresses, :realtime, [address_with_balance]}) + + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "balance_update", payload: payload}, + :timer.seconds(5) + + assert payload.address.hash == address_with_balance.hash + end + + test "not notified of balance_update if fetched_coin_balance is nil", %{address: address} do + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + Notifier.handle_event({:chain_event, :addresses, :realtime, [address]}) + + refute_receive _, 100, "Message was broadcast for nil fetched_coin_balance." + end + + test "notified of new_pending_transaction for matching from_address", %{address: address, topic: topic} do + pending = insert(:transaction, from_address: address) + + Notifier.handle_event({:chain_event, :transactions, :realtime, [pending]}) + + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "pending_transaction", payload: payload}, + :timer.seconds(5) + + assert payload.address.hash == address.hash + assert payload.transaction.hash == pending.hash + end + + test "notified of new_transaction for matching from_address", %{address: address, topic: topic} do + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction]}) + + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, :timer.seconds(5) + assert payload.address.hash == address.hash + assert payload.transaction.hash == transaction.hash + end + + test "notified of new_transaction for matching to_address", %{address: address, topic: topic} do + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction]}) + + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, :timer.seconds(5) + assert payload.address.hash == address.hash + assert payload.transaction.hash == transaction.hash + end + + test "not notified twice of new_transaction if to and from address are equal", %{address: address, topic: topic} do + transaction = + :transaction + |> insert(from_address: address, to_address: address) + |> with_block() + + Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction]}) + + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, :timer.seconds(5) + assert payload.address.hash == address.hash + assert payload.transaction.hash == transaction.hash + + refute_receive _, 100, "Received duplicate broadcast." + end + + test "notified of new_internal_transaction for matching from_address", %{address: address, topic: topic} do + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + internal_transaction = + insert( + :internal_transaction, + transaction: transaction, + from_address: address, + index: 0, + block_hash: transaction.block_hash, + block_index: 0 + ) + + Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) + + assert_receive %Phoenix.Socket.Broadcast{ + topic: ^topic, + event: "internal_transaction", + payload: %{ + address: %{hash: address_hash}, + internal_transaction: %{transaction_hash: transaction_hash, index: index} + } + }, + :timer.seconds(5) + + assert address_hash == address.hash + assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} + end + + test "notified of new_internal_transaction for matching to_address", %{address: address, topic: topic} do + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + internal_transaction = + insert(:internal_transaction, + transaction: transaction, + to_address: address, + index: 0, + block_hash: transaction.block_hash, + block_index: 0 + ) + + Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) + + assert_receive %Phoenix.Socket.Broadcast{ + topic: ^topic, + event: "internal_transaction", + payload: %{ + address: %{hash: address_hash}, + internal_transaction: %{transaction_hash: transaction_hash, index: index} + } + }, + :timer.seconds(5) + + assert address_hash == address.hash + assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} + end + + test "not notified twice of new_internal_transaction if to and from address are equal", %{ + address: address, + topic: topic + } do + transaction = + :transaction + |> insert(from_address: address, to_address: address) + |> with_block() + + internal_transaction = + insert(:internal_transaction, + transaction: transaction, + from_address: address, + to_address: address, + index: 0, + block_hash: transaction.block_hash, + block_index: 0 + ) + + Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) + + assert_receive %Phoenix.Socket.Broadcast{ + topic: ^topic, + event: "internal_transaction", + payload: %{ + address: %{hash: address_hash}, + internal_transaction: %{transaction_hash: transaction_hash, index: index} + } + }, + :timer.seconds(5) + + assert address_hash == address.hash + assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} + + refute_receive _, 100, "Received duplicate broadcast." + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/channels/block_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/block_channel_test.exs new file mode 100644 index 0000000..f9ffc01 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/channels/block_channel_test.exs @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.BlockChannelTest do + use BlockScoutWeb.ChannelCase + + alias BlockScoutWeb.Notifier + + test "subscribed user is notified of new_block event" do + topic = "blocks:new_block" + @endpoint.subscribe(topic) + + block = insert(:block, number: 1) + + Notifier.handle_event({:chain_event, :blocks, :realtime, [block]}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_block", payload: %{block: _}} -> + assert true + after + :timer.seconds(5) -> + assert false, "Expected message received nothing." + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs new file mode 100644 index 0000000..bac3c72 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs @@ -0,0 +1,100 @@ +defmodule BlockScoutWeb.ExchangeRateChannelTest do + use BlockScoutWeb.ChannelCase + + import Mox + + alias BlockScoutWeb.Notifier + alias Explorer.ExchangeRates + alias Explorer.ExchangeRates.Token + alias Explorer.ExchangeRates.Source.TestSource + alias Explorer.Market + + setup :verify_on_exit! + + setup do + # Use TestSource mock and ets table for this test set + configuration = Application.get_env(:explorer, Explorer.ExchangeRates) + Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) + Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) + + ExchangeRates.init([]) + + token = %Token{ + available_supply: Decimal.new("1000000.0"), + total_supply: Decimal.new("1000000.0"), + btc_value: Decimal.new("1.000"), + id: "test", + last_updated: DateTime.utc_now(), + market_cap_usd: Decimal.new("1000000.0"), + name: "test", + symbol: Explorer.coin(), + usd_value: Decimal.new("2.5"), + volume_24h_usd: Decimal.new("1000.0") + } + + on_exit(fn -> + Application.put_env(:explorer, Explorer.ExchangeRates, configuration) + end) + + {:ok, %{token: token}} + end + + describe "new_rate" do + test "subscribed user is notified", %{token: token} do + ExchangeRates.handle_info({nil, {:ok, [token]}}, %{}) + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()}) + + topic = "exchange_rate:new_rate" + @endpoint.subscribe(topic) + + Notifier.handle_event({:chain_event, :exchange_rate}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} -> + assert payload.exchange_rate == Map.from_struct(token) + assert payload.market_history_data == [] + after + :timer.seconds(5) -> + assert false, "Expected message received nothing." + end + end + + test "subscribed user is notified with market history", %{token: token} do + ExchangeRates.handle_info({nil, {:ok, [token]}}, %{}) + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()}) + + today = Date.utc_today() + + old_records = + for i <- 1..29 do + %{ + date: Timex.shift(today, days: i * -1), + closing_price: Decimal.new(1) + } + end + + records = [%{date: today, closing_price: token.usd_value} | old_records] + + Market.bulk_insert_history(records) + + Market.fetch_recent_history() + + topic = "exchange_rate:new_rate" + @endpoint.subscribe(topic) + + Notifier.handle_event({:chain_event, :exchange_rate}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} -> + assert payload.exchange_rate == Map.from_struct(token) + assert payload.market_history_data == records + after + :timer.seconds(5) -> + assert false, "Expected message received nothing." + end + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/channels/reward_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/reward_channel_test.exs new file mode 100644 index 0000000..6dd8dae --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/channels/reward_channel_test.exs @@ -0,0 +1,57 @@ +defmodule BlockScoutWeb.RewardChannelTest do + use BlockScoutWeb.ChannelCase, async: false + + alias BlockScoutWeb.Notifier + + describe "user subscribed to rewards" do + test "does nothing if the configuration is turned off" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + address = insert(:address) + block = insert(:block) + reward = insert(:reward, address_hash: address.hash, block_hash: block.hash) + + topic = "rewards:#{address.hash}" + @endpoint.subscribe(topic) + + Notifier.handle_event({:chain_event, :block_rewards, :realtime, [reward]}) + + refute_receive _, :timer.seconds(2) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + + test "notified of new reward for matching address" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + address = insert(:address) + block = insert(:block) + reward = insert(:reward, address_hash: address.hash, block_hash: block.hash) + + topic = "rewards:#{address.hash}" + @endpoint.subscribe(topic) + + Notifier.handle_event({:chain_event, :block_rewards, :realtime, [reward]}) + + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_reward", payload: _}, :timer.seconds(5) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + + test "not notified of new reward for other address" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + address = insert(:address) + block = insert(:block) + reward = insert(:reward, address_hash: address.hash, block_hash: block.hash) + + topic = "rewards:0x0" + @endpoint.subscribe(topic) + + Notifier.handle_event({:chain_event, :block_rewards, :realtime, [reward]}) + + refute_receive _, :timer.seconds(2) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs new file mode 100644 index 0000000..b0d5491 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs @@ -0,0 +1,63 @@ +defmodule BlockScoutWeb.TransactionChannelTest do + use BlockScoutWeb.ChannelCase + + alias Explorer.Chain.Hash + alias BlockScoutWeb.Notifier + + test "subscribed user is notified of new_transaction topic" do + topic = "transactions:new_transaction" + @endpoint.subscribe(topic) + + transaction = + :transaction + |> insert() + |> with_block() + + Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction]}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload} -> + assert payload.transaction.hash == transaction.hash + after + :timer.seconds(5) -> + assert false, "Expected message received nothing." + end + end + + test "subscribed user is notified of new_pending_transaction topic" do + topic = "transactions:new_pending_transaction" + @endpoint.subscribe(topic) + + pending = insert(:transaction) + + Notifier.handle_event({:chain_event, :transactions, :realtime, [pending]}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "pending_transaction", payload: payload} -> + assert payload.transaction.hash == pending.hash + after + :timer.seconds(5) -> + assert false, "Expected message received nothing." + end + end + + test "subscribed user is notified of transaction_hash collated event" do + transaction = + :transaction + |> insert() + |> with_block() + + topic = "transactions:#{Hash.to_string(transaction.hash)}" + @endpoint.subscribe(topic) + + Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction]}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "collated", payload: %{}} -> + assert true + after + :timer.seconds(5) -> + assert false, "Expected message received nothing." + end + end +end 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 0000000..0f56777 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs @@ -0,0 +1,919 @@ +defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do + use BlockScoutWeb.ConnCase + + 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}} + 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}} + 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 0000000..89ff276 --- /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_coin_balance_by_day_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_coin_balance_by_day_controller_test.exs new file mode 100644 index 0000000..8d94957 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_coin_balance_by_day_controller_test.exs @@ -0,0 +1,27 @@ +defmodule BlockScoutWeb.AddressCoinBalanceByDayControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.Address + + describe "GET index/2" do + test "returns the coin balance history grouped by date", %{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)) + + conn = get(conn, address_coin_balance_by_day_path(conn, :index, Address.checksum(address)), %{"type" => "JSON"}) + + response = json_response(conn, 200) + + assert [ + %{"date" => _, "value" => 2.0e-15}, + %{"date" => _, "value" => 1.0e-15} + ] = response + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs new file mode 100644 index 0000000..57e608a --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs @@ -0,0 +1,57 @@ +defmodule BlockScoutWeb.AddressContractControllerTest do + use BlockScoutWeb.ConnCase, async: true + + import BlockScoutWeb.WebRouter.Helpers, only: [address_contract_path: 3] + + alias Explorer.Chain.{Address, Hash} + alias Explorer.ExchangeRates.Token + alias Explorer.Factory + + describe "GET index/3" do + test "returns not found for nonexistent address", %{conn: conn} do + nonexistent_address_hash = Hash.to_string(Factory.address_hash()) + + conn = + get(conn, address_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(nonexistent_address_hash))) + + assert html_response(conn, 404) + end + + test "returns not found given an invalid address hash ", %{conn: conn} do + invalid_hash = "invalid_hash" + + conn = get(conn, address_contract_path(BlockScoutWeb.Endpoint, :index, invalid_hash)) + + assert html_response(conn, 404) + end + + test "returns not found when the address isn't a contract", %{conn: conn} do + address = insert(:address) + + conn = get(conn, address_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address))) + + assert html_response(conn, 404) + end + + test "successfully renders the page when the address is a contract", %{conn: conn} do + address = insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil) + + transaction = insert(:transaction, from_address: address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + conn = get(conn, address_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address))) + + assert html_response(conn, 200) + assert address.hash == conn.assigns.address.hash + assert %Token{} = conn.assigns.exchange_rate + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs new file mode 100644 index 0000000..10c133d --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs @@ -0,0 +1,97 @@ +defmodule BlockScoutWeb.AddressControllerTest do + use BlockScoutWeb.ConnCase, + # ETS tables are shared in `Explorer.Counters.*` + async: false + + import Mox + + alias Explorer.Chain.Address + alias Explorer.Counters.{AddressesCounter} + + describe "GET index/2" do + setup :set_mox_global + + setup do + # Use TestSource mock for this test set + configuration = Application.get_env(:block_scout_web, :show_percentage) + Application.put_env(:block_scout_web, :show_percentage, false) + + :ok + + on_exit(fn -> + Application.put_env(:block_scout_web, :show_percentage, configuration) + end) + end + + test "returns top addresses", %{conn: conn} do + address_hashes = + 4..1 + |> Enum.map(&insert(:address, fetched_coin_balance: &1)) + |> Enum.map(& &1.hash) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + conn = get(conn, address_path(conn, :index, %{type: "JSON"})) + {:ok, %{"items" => items}} = Poison.decode(conn.resp_body) + + assert Enum.count(items) == Enum.count(address_hashes) + end + + test "returns an address's primary name when present", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 1) + insert(:address_name, address: address, primary: true, name: "POA Wallet") + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + conn = get(conn, address_path(conn, :index, %{type: "JSON"})) + + {:ok, %{"items" => [item]}} = Poison.decode(conn.resp_body) + + assert String.contains?(item, "POA Wallet") + end + end + + describe "GET show/3" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:explorer, :checksum_function) + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, :checksum_function, configuration) + end) + end + + test "redirects to address/:address_id/transactions", %{conn: conn} do + insert(:address, hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + + conn = get(conn, "/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") + + assert html_response(conn, 200) + end + end + + describe "GET address-counters/2" do + test "returns address counters", %{conn: conn} do + address = insert(:address) + + conn = get(conn, "/address-counters", %{"id" => Address.checksum(address.hash)}) + + assert conn.status == 200 + {:ok, response} = Jason.decode(conn.resp_body) + + assert %{ + "transaction_count" => 0, + "token_transfer_count" => 0, + "validation_count" => 0, + "gas_usage_count" => 0 + } == + response + 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 new file mode 100644 index 0000000..a78aff7 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs @@ -0,0 +1,405 @@ +defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do + use BlockScoutWeb.ConnCase, async: true + + import BlockScoutWeb.WebRouter.Helpers, + only: [address_internal_transaction_path: 3, address_internal_transaction_path: 4] + + alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction} + alias Explorer.ExchangeRates.Token + + describe "GET index/3" do + test "with invalid address hash", %{conn: conn} do + conn = + conn + |> get(address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with valid address hash without address", %{conn: conn} do + conn = + get( + conn, + address_internal_transaction_path( + conn, + :index, + Address.checksum("0x8bf38d4764929064f2d4d3a56520a76ab3df415b") + ) + ) + + assert html_response(conn, 200) + end + + test "includes USD exchange rate value for address in assigns", %{conn: conn} do + address = insert(:address) + + conn = + get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash))) + + assert %Token{} = conn.assigns.exchange_rate + end + + test "returns internal transactions for the address", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + from_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + from_address: address, + index: 1, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 1 + ) + + to_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + to_address: address, + index: 2, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 2 + ) + + path = address_internal_transaction_path(conn, :index, Address.checksum(address), %{"type" => "JSON"}) + conn = get(conn, path) + + internal_transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.all?([from_internal_transaction, to_internal_transaction], fn internal_transaction -> + Enum.any?(internal_transaction_tiles, 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 "returns internal transactions coming from the address", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + from_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + from_address: address, + index: 1, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 1 + ) + + to_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + to_address: address, + index: 2, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 2 + ) + + path = + address_internal_transaction_path(conn, :index, Address.checksum(address), %{ + "filter" => "from", + "type" => "JSON" + }) + + conn = get(conn, path) + + internal_transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"") + end) + + refute Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"") + end) + end + + test "returns internal transactions going to the address", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + from_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + from_address: address, + index: 1, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 1 + ) + + to_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + to_address: address, + index: 2, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 2 + ) + + path = + address_internal_transaction_path(conn, :index, Address.checksum(address), %{"filter" => "to", "type" => "JSON"}) + + conn = get(conn, path) + + internal_transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"") + end) + + refute Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"") + end) + end + + test "returns internal an transaction that created the address", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + from_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + from_address: address, + index: 1, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 1 + ) + + to_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + to_address: nil, + created_contract_address: address, + index: 2, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 2 + ) + + path = + address_internal_transaction_path(conn, :index, Address.checksum(address), %{"filter" => "to", "type" => "JSON"}) + + conn = get(conn, path) + + internal_transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"") + end) + + refute Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"") + end) + end + + test "returns next page of results based on last seen internal transaction", %{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) + + transaction_1_hashes = + 1..20 + |> 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) + + transaction_2_hashes = + 1..20 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction_2, + from_address: address, + index: index, + block_number: transaction_2.block_number, + transaction_index: transaction_2.index, + block_hash: a_block.hash, + block_index: 20 + index + ) + end) + + transaction_3_hashes = + 1..10 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction_3, + from_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 = transaction_1_hashes ++ transaction_2_hashes ++ transaction_3_hashes + + %InternalTransaction{index: index} = + insert( + :internal_transaction, + transaction: transaction_3, + from_address: address, + index: 11, + block_number: transaction_3.block_number, + transaction_index: transaction_3.index, + block_hash: b_block.hash, + block_index: 11 + ) + + conn = + get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), %{ + "block_number" => Integer.to_string(b_block.number), + "transaction_index" => Integer.to_string(transaction_3.index), + "index" => Integer.to_string(index), + "type" => "JSON" + }) + + internal_transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.all?(second_page, fn internal_transaction -> + Enum.any?(internal_transaction_tiles, 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) + + transaction = + %Transaction{index: transaction_index} = + :transaction + |> insert() + |> with_block(block) + + 1..60 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction, + from_address: address, + index: index, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: index + ) + end) + + conn = + get( + conn, + address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash), %{ + "type" => "JSON" + }) + ) + + expected_response = + address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash, %{ + "block_number" => number, + "index" => 11, + "transaction_index" => transaction_index, + "items_count" => "50" + }) + + assert expected_response == json_response(conn, 200)["next_page_path"] + end + + test "next_page_params are empty if on last page", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + 1..2 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction, + from_address: address, + index: index, + block_hash: transaction.block_hash, + block_index: index + ) + end) + + conn = + get( + conn, + address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash), %{ + "type" => "JSON" + }) + ) + + assert %{"next_page_path" => nil} = json_response(conn, 200) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs new file mode 100644 index 0000000..6a01c12 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs @@ -0,0 +1,130 @@ +defmodule BlockScoutWeb.AddressReadContractControllerTest do + use BlockScoutWeb.ConnCase, async: true + use ExUnit.Case, async: false + + alias Explorer.ExchangeRates.Token + alias Explorer.Chain.Address + + import Mox + + describe "GET index/3" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:explorer, :checksum_function) + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, :checksum_function, configuration) + end) + end + + test "with invalid address hash", %{conn: conn} do + conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with valid address that is not a contract", %{conn: conn} do + address = insert(:address) + + conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash))) + + assert html_response(conn, 404) + end + + test "successfully renders the page when the address is a contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") + + get_eip1967_implementation() + + conn = + get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 200) + assert contract_address.hash == conn.assigns.address.hash + assert %Token{} = conn.assigns.exchange_rate + end + + test "returns not found for an unverified contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + conn = + get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 404) + end + end + + def get_eip1967_implementation do + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} + end + ) + |> 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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs new file mode 100644 index 0000000..4551ef0 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs @@ -0,0 +1,128 @@ +defmodule BlockScoutWeb.AddressReadProxyControllerTest do + use BlockScoutWeb.ConnCase, async: true + use ExUnit.Case, async: false + + alias Explorer.ExchangeRates.Token + alias Explorer.Chain.Address + + import Mox + + describe "GET index/3" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:explorer, :checksum_function) + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, :checksum_function, configuration) + end) + end + + test "with invalid address hash", %{conn: conn} do + conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with valid address that is not a contract", %{conn: conn} do + address = insert(:address) + + conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash))) + + assert html_response(conn, 404) + end + + test "successfully renders the page when the address is a contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") + + get_eip1967_implementation() + + conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 200) + assert contract_address.hash == conn.assigns.address.hash + assert %Token{} = conn.assigns.exchange_rate + end + + test "returns not found for an unverified contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 404) + end + end + + def get_eip1967_implementation do + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} + end + ) + |> 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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs new file mode 100644 index 0000000..cdafe88 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.Address + alias Explorer.Factory + + describe "GET index/3" do + test "without AJAX", %{conn: conn} do + %Address{hash: hash} = Factory.insert(:address) + + response_conn = get(conn, address_token_balance_path(conn, :index, Address.checksum(hash))) + + assert html_response(response_conn, 404) + end + + test "with AJAX without valid address", %{conn: conn} do + ajax_conn = ajax(conn) + + response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, "invalid_address")) + + assert html_response(response_conn, 404) + end + + test "with AJAX with valid address without address still returns token balances", %{conn: conn} do + ajax_conn = ajax(conn) + + response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, Address.checksum(address_hash()))) + + assert html_response(response_conn, 200) + end + + test "with AJAX with valid address with address returns token balances", %{conn: conn} do + %Address{hash: hash} = Factory.insert(:address) + + ajax_conn = ajax(conn) + + response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, Address.checksum(hash))) + + assert html_response(response_conn, 200) + end + end + + defp ajax(conn) do + put_req_header(conn, "x-requested-with", "XMLHttpRequest") + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs new file mode 100644 index 0000000..c547886 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs @@ -0,0 +1,172 @@ +defmodule BlockScoutWeb.AddressTokenControllerTest do + use BlockScoutWeb.ConnCase, async: true + use ExUnit.Case, async: false + + import BlockScoutWeb.WebRouter.Helpers, only: [address_token_path: 3] + import Mox + + alias Explorer.Chain.{Address, Token} + + describe "GET index/2" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:explorer, :checksum_function) + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, :checksum_function, configuration) + end) + end + + test "with invalid address hash", %{conn: conn} do + conn = get(conn, address_token_path(conn, :index, "invalid_address")) + + assert html_response(conn, 422) + end + + test "with valid address hash without address", %{conn: conn} do + conn = get(conn, address_token_path(conn, :index, Address.checksum("0x8bf38d4764929064f2d4d3a56520a76ab3df415b"))) + + assert html_response(conn, 404) + end + + test "returns tokens that have balance for the address", %{conn: conn} do + address = insert(:address) + + token1 = + :token + |> insert(name: "token1") + + token2 = + :token + |> insert(name: "token2") + + insert( + :address_current_token_balance, + address: address, + token_contract_address_hash: token1.contract_address_hash, + value: 1000 + ) + + insert( + :address_current_token_balance, + address: address, + token_contract_address_hash: token2.contract_address_hash, + value: 0 + ) + + insert( + :token_transfer, + token_contract_address: token1.contract_address, + from_address: address, + to_address: build(:address) + ) + + insert( + :token_transfer, + token_contract_address: token2.contract_address, + from_address: build(:address), + to_address: address + ) + + conn = get(conn, address_token_path(conn, :index, Address.checksum(address)), type: "JSON") + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert json_response(conn, 200) + assert Enum.any?(items, fn item -> String.contains?(item, to_string(token1.contract_address_hash)) end) + refute Enum.any?(items, fn item -> String.contains?(item, to_string(token2.contract_address_hash)) end) + end + + test "returns next page of results based on last seen token", %{conn: conn} do + address = insert(:address) + + second_page_tokens = + 1..50 + |> Enum.reduce([], fn i, acc -> + token = insert(:token, name: "A Token#{i}", type: "ERC-20") + + insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: address, + value: 1000 + ) + + acc ++ [token.name] + end) + |> Enum.sort() + + token = insert(:token, name: "Another Token", type: "ERC-721") + + insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: address, + value: 1000 + ) + + %Token{name: name, type: type, inserted_at: _inserted_at} = token + + conn = + get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), %{ + "token_name" => name, + "token_type" => type, + "value" => 1000, + "type" => "JSON" + }) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.any?(items, fn item -> + Enum.any?(second_page_tokens, fn token_name -> String.contains?(item, token_name) end) + end) + end + + test "next_page_params exists if not on last page", %{conn: conn} do + address = insert(:address) + + Enum.each(1..51, fn i -> + token = insert(:token, name: "A Token#{i}", type: "ERC-20") + + insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: address, + value: 1000 + ) + + insert(:token_transfer, token_contract_address: token.contract_address, from_address: address) + end) + + conn = get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), type: "JSON") + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + assert next_page_path + end + + test "next_page_params are empty if on last page", %{conn: conn} do + address = insert(:address) + token = insert(:token) + insert(:token_transfer, token_contract_address: token.contract_address, from_address: address) + + conn = get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), type: "JSON") + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + refute next_page_path + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs new file mode 100644 index 0000000..f8a6602 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs @@ -0,0 +1,274 @@ +defmodule BlockScoutWeb.AddressTokenTransferControllerTest do + use BlockScoutWeb.ConnCase + + import BlockScoutWeb.WebRouter.Helpers, + only: [address_token_transfers_path: 4, address_token_transfers_path: 5] + + alias Explorer.Chain.{Address, Token} + + describe "GET index/2" do + test "with invalid address hash", %{conn: conn} do + token_hash = "0xc8982771dd50285389c352c175ada74d074427c7" + conn = get(conn, address_token_transfers_path(conn, :index, "invalid_address", token_hash)) + + assert html_response(conn, 422) + end + + test "with invalid token hash", %{conn: conn} do + address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + + conn = get(conn, address_token_transfers_path(conn, :index, Address.checksum(address_hash), "invalid_address")) + + assert html_response(conn, 422) + end + + test "with an address that doesn't exist in our database", %{conn: conn} do + address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + %Token{contract_address_hash: token_hash} = insert(:token) + + conn = + get( + conn, + address_token_transfers_path(conn, :index, Address.checksum(address_hash), Address.checksum(token_hash)) + ) + + assert html_response(conn, 404) + end + + test "with an token that doesn't exist in our database", %{conn: conn} do + %Address{hash: address_hash} = insert(:address) + token_hash = Address.checksum("0x8bf38d4764929064f2d4d3a56520a76ab3df415b") + conn = get(conn, address_token_transfers_path(conn, :index, Address.checksum(address_hash), token_hash)) + + assert html_response(conn, 404) + end + end + + describe "GET index/2 JSON" do + test "without token transfers for a token", %{conn: conn} do + %Address{hash: address_hash} = insert(:address) + %Token{contract_address_hash: token_hash} = insert(:token) + + conn = + get( + conn, + address_token_transfers_path(conn, :index, Address.checksum(address_hash), Address.checksum(token_hash)), + %{ + type: "JSON" + } + ) + + assert json_response(conn, 200) == %{"items" => [], "next_page_path" => nil} + end + + test "returns the correct number of transactions", %{conn: conn} do + address = insert(:address) + token = insert(:token) + + inserted_transactions = + Enum.map(1..5, fn index -> + block = insert(:block, number: 1000 - index) + + transaction = + :transaction + |> insert() + |> with_block(block) + + insert( + :token_transfer, + to_address: address, + transaction: transaction, + token_contract_address: token.contract_address + ) + + transaction + end) + + conn = + get( + conn, + address_token_transfers_path( + conn, + :index, + Address.checksum(address.hash), + Address.checksum(token.contract_address_hash) + ), + %{type: "JSON"} + ) + + response_items = + conn + |> json_response(200) + |> Map.get("items") + + items_length = length(response_items) + + assert items_length == 5 + + assert Enum.all?(inserted_transactions, fn transaction -> + Enum.any?(response_items, fn item -> + String.contains?( + item, + "data-identifier-hash=\"#{to_string(transaction.hash)}\"" + ) + end) + end) + end + + test "returns next_page_path as null when there are no more pages", %{conn: conn} do + address = insert(:address) + token = insert(:token) + + block = insert(:block, number: 1000) + + transaction = + :transaction + |> insert() + |> with_block(block) + + insert( + :token_transfer, + to_address: address, + transaction: transaction, + token_contract_address: token.contract_address + ) + + conn = + get( + conn, + address_token_transfers_path( + conn, + :index, + Address.checksum(address.hash), + Address.checksum(token.contract_address_hash) + ), + %{type: "JSON"} + ) + + assert Map.get(json_response(conn, 200), "next_page_path") == nil + end + + test "returns next_page_path when there are more items", %{conn: conn} do + address = insert(:address) + token = insert(:token) + + page_last_transfer = + 1..50 + |> Enum.map(fn index -> + block = insert(:block, number: 1000 - index) + + transaction = + :transaction + |> insert() + |> with_block(block) + + insert( + :token_transfer, + to_address: address, + transaction: transaction, + token_contract_address: token.contract_address + ) + + transaction + end) + |> List.last() + + Enum.each(51..60, fn index -> + block = insert(:block, number: 1000 - index) + + transaction = + :transaction + |> insert() + |> with_block(block) + + insert( + :token_transfer, + to_address: address, + transaction: transaction, + token_contract_address: token.contract_address + ) + end) + + conn = + get( + conn, + address_token_transfers_path( + conn, + :index, + Address.checksum(address.hash), + Address.checksum(token.contract_address_hash) + ), + %{type: "JSON"} + ) + + expected_path = + address_token_transfers_path( + conn, + :index, + Address.checksum(address.hash), + Address.checksum(token.contract_address_hash), + %{ + block_number: page_last_transfer.block_number, + index: page_last_transfer.index, + items_count: "50" + } + ) + + assert Map.get(json_response(conn, 200), "next_page_path") == expected_path + end + + test "with invalid address hash", %{conn: conn} do + token_hash = "0xc8982771dd50285389c352c175ada74d074427c7" + + conn = + get(conn, address_token_transfers_path(conn, :index, "invalid_address", token_hash), %{ + type: "JSON" + }) + + assert html_response(conn, 422) + end + + test "with invalid token hash", %{conn: conn} do + address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + + conn = + get(conn, address_token_transfers_path(conn, :index, Address.checksum(address_hash), "invalid_address"), %{ + type: "JSON" + }) + + assert html_response(conn, 422) + end + + test "with an address that doesn't exist in our database", %{conn: conn} do + address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + %Token{contract_address_hash: token_hash} = insert(:token) + + conn = + get( + conn, + address_token_transfers_path(conn, :index, Address.checksum(address_hash), Address.checksum(token_hash)), + %{ + type: "JSON" + } + ) + + assert html_response(conn, 404) + end + + test "with a token that doesn't exist in our database", %{conn: conn} do + %Address{hash: address_hash} = insert(:address) + token_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + + conn = + get( + conn, + address_token_transfers_path(conn, :index, Address.checksum(address_hash), Address.checksum(token_hash)), + %{ + type: "JSON" + } + ) + + assert html_response(conn, 404) + end + end +end 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 new file mode 100644 index 0000000..159c1be --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -0,0 +1,405 @@ +defmodule BlockScoutWeb.AddressTransactionControllerTest do + use BlockScoutWeb.ConnCase, async: true + use ExUnit.Case, async: false + + import BlockScoutWeb.WebRouter.Helpers, only: [address_transaction_path: 3, address_transaction_path: 4] + import Mox + + alias Explorer.Chain.{Address, Transaction} + alias Explorer.ExchangeRates.Token + + setup :verify_on_exit! + + describe "GET index/2" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:explorer, :checksum_function) + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, :checksum_function, configuration) + end) + end + + test "with invalid address hash", %{conn: conn} do + conn = get(conn, address_transaction_path(conn, :index, "invalid_address")) + + assert html_response(conn, 422) + end + + test "with valid address hash without address in the DB", %{conn: conn} do + conn = + get( + conn, + address_transaction_path(conn, :index, Address.checksum("0x8bf38d4764929064f2d4d3a56520a76ab3df415b"), %{ + "type" => "JSON" + }) + ) + + assert json_response(conn, 200) + transaction_tiles = json_response(conn, 200)["items"] + assert transaction_tiles |> length() == 0 + end + + test "returns transactions for the address", %{conn: conn} do + address = insert(:address) + + block = insert(:block) + + from_transaction = + :transaction + |> insert(from_address: address) + |> with_block(block) + + to_transaction = + :transaction + |> insert(to_address: address) + |> with_block(block) + + conn = get(conn, address_transaction_path(conn, :index, Address.checksum(address), %{"type" => "JSON"})) + + transaction_tiles = json_response(conn, 200)["items"] + transaction_hashes = Enum.map([to_transaction.hash, from_transaction.hash], &to_string(&1)) + + assert Enum.all?(transaction_hashes, fn transaction_hash -> + Enum.any?(transaction_tiles, &String.contains?(&1, transaction_hash)) + end) + end + + test "includes USD exchange rate value for address in assigns", %{conn: conn} do + address = insert(:address) + + conn = get(conn, address_transaction_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash))) + + assert %Token{} = conn.assigns.exchange_rate + end + + test "returns next page of results based on last seen transaction", %{conn: conn} do + address = insert(:address) + + second_page_hashes = + 50 + |> insert_list(:transaction, from_address: address) + |> with_block() + |> Enum.map(& &1.hash) + + %Transaction{block_number: block_number, index: index} = + :transaction + |> insert(from_address: address) + |> with_block() + + conn = + get(conn, address_transaction_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), %{ + "block_number" => Integer.to_string(block_number), + "index" => Integer.to_string(index), + "type" => "JSON" + }) + + transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.all?(second_page_hashes, fn address_hash -> + Enum.any?(transaction_tiles, &String.contains?(&1, to_string(address_hash))) + end) + end + + test "next_page_params exist if not on last page", %{conn: conn} do + address = insert(:address) + block = insert(:block) + + 60 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + + conn = get(conn, address_transaction_path(conn, :index, Address.checksum(address.hash), %{"type" => "JSON"})) + + assert json_response(conn, 200)["next_page_path"] + end + + test "next_page_params are empty if on last page", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + conn = get(conn, address_transaction_path(conn, :index, Address.checksum(address.hash), %{"type" => "JSON"})) + + refute json_response(conn, 200)["next_page_path"] + end + + test "returns parent transaction for a contract address", %{conn: conn} do + address = insert(:address, contract_code: data(:address_contract_code)) + block = insert(:block) + + transaction = + :transaction + |> insert(to_address: nil, created_contract_address_hash: address.hash) + |> with_block(block) + |> Explorer.Repo.preload([[created_contract_address: :names], [from_address: :names], :token_transfers]) + + insert( + :internal_transaction_create, + index: 0, + created_contract_address: address, + to_address: nil, + transaction: transaction, + block_hash: block.hash, + block_index: 0 + ) + + conn = get(conn, address_transaction_path(conn, :index, Address.checksum(address)), %{"type" => "JSON"}) + + transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.all?([transaction.hash], fn transaction_hash -> + Enum.any?(transaction_tiles, &String.contains?(&1, to_string(transaction_hash))) + end) + end + end + + describe "GET token-transfers-csv/2" 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 = + :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 + }) + + 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 + |> insert(from_address: address) + |> with_block() + + :transaction + |> insert(from_address: address) + |> with_block() + + from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) + to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) + + conn = + get(conn, "/transactions-csv", %{ + "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 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 = + :transaction + |> insert() + |> with_block() + + transaction_2 = + :transaction + |> insert() + |> with_block() + + transaction_3 = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + index: 3, + transaction: transaction_1, + from_address: address, + block_number: transaction_1.block_number, + block_hash: transaction_1.block_hash, + block_index: 0, + transaction_index: transaction_1.index + ) + + insert(:internal_transaction, + index: 1, + transaction: transaction_2, + to_address: address, + block_number: transaction_2.block_number, + block_hash: transaction_2.block_hash, + block_index: 1, + transaction_index: transaction_2.index + ) + + insert(:internal_transaction, + index: 2, + transaction: transaction_3, + created_contract_address: address, + block_number: transaction_3.block_number, + block_hash: transaction_3.block_hash, + block_index: 2, + transaction_index: transaction_3.index + ) + + from_period = Timex.format!(Timex.shift(Timex.now(), years: -1), "%Y-%m-%d", :strftime) + to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) + + conn = + get(conn, "/internal-transactions-csv", %{ + "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() == 5 + end + end + + 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 = + :transaction + |> insert() + |> with_block() + + insert(:log, + address: address, + index: 3, + transaction: transaction_1, + block: transaction_1.block, + block_number: transaction_1.block_number + ) + + transaction_2 = + :transaction + |> insert() + |> with_block() + + insert(:log, + address: address, + index: 1, + transaction: transaction_2, + block: transaction_2.block, + block_number: transaction_2.block_number + ) + + transaction_3 = + :transaction + |> insert() + |> with_block() + + insert(:log, + address: address, + index: 2, + transaction: transaction_3, + block: transaction_3.block, + block_number: transaction_3.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, "/logs-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() == 5 + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs new file mode 100644 index 0000000..72b9a8e --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs @@ -0,0 +1,132 @@ +defmodule BlockScoutWeb.AddressWriteContractControllerTest do + use BlockScoutWeb.ConnCase, async: true + use ExUnit.Case, async: false + + alias Explorer.ExchangeRates.Token + alias Explorer.Chain.Address + + use EthereumJSONRPC.Case, async: false + + import Mox + + describe "GET index/3" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:explorer, :checksum_function) + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, :checksum_function, configuration) + end) + end + + test "with invalid address hash", %{conn: conn} do + conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with valid address that is not a contract", %{conn: conn} do + address = insert(:address) + + conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash))) + + assert html_response(conn, 404) + end + + test "successfully renders the page when the address is a contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") + + get_eip1967_implementation() + + conn = + get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 200) + assert contract_address.hash == conn.assigns.address.hash + assert %Token{} = conn.assigns.exchange_rate + end + + test "returns not found for an unverified contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + conn = + get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 404) + end + end + + def get_eip1967_implementation do + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} + end + ) + |> 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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs new file mode 100644 index 0000000..3000235 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs @@ -0,0 +1,130 @@ +defmodule BlockScoutWeb.AddressWriteProxyControllerTest do + use BlockScoutWeb.ConnCase, async: true + use ExUnit.Case, async: false + + alias Explorer.ExchangeRates.Token + alias Explorer.Chain.Address + + import Mox + + describe "GET index/3" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:explorer, :checksum_function) + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, :checksum_function, configuration) + end) + end + + test "with invalid address hash", %{conn: conn} do + conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with valid address that is not a contract", %{conn: conn} do + address = insert(:address) + + conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash))) + + assert html_response(conn, 404) + end + + test "successfully renders the page when the address is a contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") + + get_eip1967_implementation() + + conn = + get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 200) + assert contract_address.hash == conn.assigns.address.hash + assert %Token{} = conn.assigns.exchange_rate + end + + test "returns not found for an unverified contract", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = insert(:transaction, from_address: contract_address) |> with_block() + + insert( + :internal_transaction_create, + index: 0, + transaction: transaction, + created_contract_address: contract_address, + block_hash: transaction.block_hash, + block_index: 0 + ) + + conn = + get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) + + assert html_response(conn, 404) + end + end + + def get_eip1967_implementation do + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} + end + ) + |> 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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/admin/dashboard_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/admin/dashboard_controller_test.exs new file mode 100644 index 0000000..03cc0a3 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/admin/dashboard_controller_test.exs @@ -0,0 +1,26 @@ +defmodule BlockScoutWeb.Admin.DashboardControllerTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.Router + + describe "index/2" do + setup %{conn: conn} do + admin = insert(:administrator) + + conn = + conn + |> bypass_through(Router, [:browser]) + |> get("/") + |> put_session(:user_id, admin.user.id) + |> send_resp(200, "") + |> recycle() + + {:ok, conn: conn} + end + + test "shows the dashboard page", %{conn: conn} do + result = get(conn, "/admin" <> AdminRoutes.dashboard_path(conn, :index)) + assert html_response(result, 200) =~ "administrator_dashboard" + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/admin/session_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/admin/session_controller_test.exs new file mode 100644 index 0000000..ce6e4e5 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/admin/session_controller_test.exs @@ -0,0 +1,83 @@ +defmodule BlockScoutWeb.Admin.SessionControllerTest do + use BlockScoutWeb.ConnCase + + setup %{conn: conn} do + conn = + conn + |> bypass_through() + |> get("/") + + {:ok, conn: conn} + end + + describe "new/2" do + test "redirects to setup page if not configured", %{conn: conn} do + result = get(conn, AdminRoutes.session_path(conn, :new)) + assert redirected_to(result) == AdminRoutes.setup_path(conn, :configure) + end + + test "shows the admin login page", %{conn: conn} do + insert(:administrator) + result = get(conn, AdminRoutes.session_path(conn, :new)) + assert html_response(result, 200) =~ "administrator_login" + end + end + + describe "create/2" do + test "redirects to setup page if not configured", %{conn: conn} do + result = post(conn, AdminRoutes.session_path(conn, :create), %{}) + assert redirected_to(result) == AdminRoutes.setup_path(conn, :configure) + end + + test "redirects to dashboard on successful admin login", %{conn: conn} do + admin = insert(:administrator) + + params = %{ + "authenticate" => %{ + username: admin.user.username, + password: "password" + } + } + + result = post(conn, AdminRoutes.session_path(conn, :create), params) + assert redirected_to(result) == AdminRoutes.dashboard_path(conn, :index) + end + + test "reshows form if params are invalid", %{conn: conn} do + insert(:administrator) + params = %{"authenticate" => %{}} + + result = post(conn, AdminRoutes.session_path(conn, :create), params) + assert html_response(result, 200) =~ "administrator_login" + end + + test "reshows form if credentials are invalid", %{conn: conn} do + admin = insert(:administrator) + + params = %{ + "authenticate" => %{ + username: admin.user.username, + password: "badpassword" + } + } + + result = post(conn, AdminRoutes.session_path(conn, :create), params) + assert html_response(result, 200) =~ "administrator_login" + end + + test "reshows form if user is not an admin", %{conn: conn} do + insert(:administrator) + user = insert(:user) + + params = %{ + "authenticate" => %{ + username: user.username, + password: "password" + } + } + + result = post(conn, AdminRoutes.session_path(conn, :create), params) + assert html_response(result, 200) =~ "administrator_login" + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/admin/setup_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/admin/setup_controller_test.exs new file mode 100644 index 0000000..55c4753 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/admin/setup_controller_test.exs @@ -0,0 +1,109 @@ +defmodule BlockScoutWeb.Admin.SetupControllerTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.Admin.SetupController + alias Explorer.Admin + + setup %{conn: conn} do + conn = + conn + |> bypass_through() + |> get("/") + + {:ok, conn: conn} + end + + describe "configure/2" do + test "redirects to session page if already configured", %{conn: conn} do + insert(:administrator) + result = get(conn, AdminRoutes.setup_path(conn, :configure)) + assert redirected_to(result) == AdminRoutes.session_path(conn, :new) + end + end + + describe "configure/2 with no params" do + test "shows the verification page", %{conn: conn} do + result = get(conn, AdminRoutes.setup_path(conn, :configure)) + assert html_response(result, 200) =~ "administrator_verify" + end + end + + describe "configure/2 with state param" do + test "shows verification page when state is invalid", %{conn: conn} do + result = get(conn, AdminRoutes.setup_path(conn, :configure), %{state: ""}) + assert html_response(result, 200) =~ "administrator_verify" + end + + test "shows registration page when state is valid", %{conn: conn} do + state = SetupController.generate_secure_token() + result = get(conn, AdminRoutes.setup_path(conn, :configure), %{state: state}) + assert html_response(result, 200) =~ "administrator_registration" + end + end + + describe "configure_admin/2" do + test "redirects to session page if already configured", %{conn: conn} do + insert(:administrator) + result = post(conn, AdminRoutes.setup_path(conn, :configure), %{}) + assert redirected_to(result) == AdminRoutes.session_path(conn, :new) + end + end + + describe "configure_admin/2 with no params" do + test "reshows the verification page", %{conn: conn} do + result = post(conn, AdminRoutes.setup_path(conn, :configure_admin), %{}) + assert html_response(result, 200) =~ "administrator_verify" + end + end + + describe "configure_admin/2 with verify param" do + test "redirects with valid recovery key", %{conn: conn} do + key = Admin.recovery_key() + params = %{verify: %{recovery_key: key}} + result = post(conn, AdminRoutes.setup_path(conn, :configure_admin), params) + assert redirected_to(result) =~ AdminRoutes.setup_path(conn, :configure, %{state: ""}) + end + + test "reshows the verification page with invalid key", %{conn: conn} do + params = %{verify: %{recovery_key: "bad_key"}} + result = post(conn, AdminRoutes.setup_path(conn, :configure_admin), params) + assert html_response(result, 200) =~ "administrator_verify" + end + end + + describe "configure_admin with state and registration params" do + setup do + [state: SetupController.generate_secure_token()] + end + + test "reshows the verification page when state is invalid", %{conn: conn} do + params = %{state: "invalid_state", registration: %{}} + result = post(conn, AdminRoutes.setup_path(conn, :configure_admin), params) + assert html_response(result, 200) =~ "administrator_verify" + end + + test "reshows the registration page when registration is invalid", %{conn: conn, state: state} do + params = %{state: state, registration: %{}} + result = post(conn, AdminRoutes.setup_path(conn, :configure_admin), params) + response = html_response(result, 200) + assert response =~ "administrator_registration" + assert response =~ "invalid-feedback" + assert response =~ "is-invalid" + end + + test "redirects to dashboard when state and registration are valid", %{conn: conn, state: state} do + params = %{ + state: state, + registration: %{ + username: "admin_user", + email: "admin_user@blockscout", + password: "testpassword", + password_confirmation: "testpassword" + } + } + + result = post(conn, AdminRoutes.setup_path(conn, :configure_admin), params) + assert redirected_to(result) == AdminRoutes.dashboard_path(conn, :index) + end + end +end 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 new file mode 100644 index 0000000..1a1b7fa --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -0,0 +1,3164 @@ +defmodule BlockScoutWeb.API.RPC.AddressControllerTest do + use BlockScoutWeb.ConnCase, async: false + + import Mox + + alias BlockScoutWeb.API.RPC.AddressController + alias Explorer.Chain + alias Explorer.Chain.{Events.Subscriber, Transaction, Wei} + alias Explorer.Counters.{AddressesCounter, AverageBlockTime} + alias Indexer.Fetcher.CoinBalanceOnDemand + alias Explorer.Repo + + setup :set_mox_global + setup :verify_on_exit! + + setup do + mocked_json_rpc_named_arguments = [ + transport: EthereumJSONRPC.Mox, + transport_options: [] + ] + + start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) + start_supervised!(AverageBlockTime) + start_supervised!({CoinBalanceOnDemand, [mocked_json_rpc_named_arguments, [name: CoinBalanceOnDemand]]}) + start_supervised!(AddressesCounter) + + Application.put_env(:explorer, AverageBlockTime, enabled: true) + + on_exit(fn -> + Application.put_env(:explorer, AverageBlockTime, enabled: false) + end) + + :ok + end + + describe "listaccounts" do + setup do + Subscriber.to(:addresses, :on_demand) + Subscriber.to(:address_coin_balances, :on_demand) + + %{params: %{"module" => "account", "action" => "listaccounts"}} + end + + test "with no addresses", %{params: params, conn: conn} do + 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 response["result"] == [] + end + + test "with existing addresses", %{params: params, conn: conn} do + first_address = insert(:address, fetched_coin_balance: 10, inserted_at: Timex.shift(Timex.now(), minutes: -10)) + second_address = insert(:address, fetched_coin_balance: 100, inserted_at: Timex.shift(Timex.now(), minutes: -5)) + first_address_hash = to_string(first_address.hash) + second_address_hash = to_string(second_address.hash) + + 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, + "balance" => "10" + }, + %{ + "address" => ^second_address_hash, + "balance" => "100" + } + ] = 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() + + mining_address = + insert(:address, + fetched_coin_balance: 0, + fetched_coin_balance_block_number: 102, + inserted_at: Timex.shift(now, minutes: -10) + ) + + mining_address_hash = to_string(mining_address.hash) + # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far + # back we'd need to go to get 24 hours in the past) + Enum.each(0..100, fn i -> + insert(:block, number: i, timestamp: Timex.shift(now, hours: -(102 - i) * 25), miner: mining_address) + end) + + insert(:block, number: 101, timestamp: Timex.shift(now, hours: -25), miner: mining_address) + AverageBlockTime.refresh() + + address = + insert(:address, + fetched_coin_balance: 100, + fetched_coin_balance_block_number: 100, + inserted_at: Timex.shift(now, minutes: -5) + ) + + address_hash = to_string(address.hash) + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [ + %{ + id: id, + method: "eth_getBalance", + params: [^mining_address_hash, "0x65"] + } + ], + _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} + end) + + res = eth_block_number_fake_response("0x65") + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [ + %{ + id: 0, + method: "eth_getBlockByNumber", + params: ["0x65", true] + } + ], + _ -> + {:ok, [res]} + end) + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [ + %{ + id: id, + method: "eth_getBalance", + params: [^address_hash, "0x65"] + } + ], + _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} + end) + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [ + %{ + id: 0, + method: "eth_getBlockByNumber", + params: ["0x65", true] + } + ], + _ -> + {:ok, [res]} + end) + + 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" => ^mining_address_hash, + "balance" => "0", + "stale" => false + }, + %{ + "address" => ^address_hash, + "balance" => "100", + "stale" => true + } + ] = response["result"] + + {:ok, expected_wei} = Wei.cast(2) + + assert_receive({:chain_event, :addresses, :on_demand, [received_address]}) + + assert received_address.hash == address.hash + assert received_address.fetched_coin_balance == expected_wei + assert received_address.fetched_coin_balance_block_number == 101 + end + end + + describe "balance" do + test "with missing address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "balance" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["message"] =~ "'address' is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "balance", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["message"] =~ "Invalid address hash" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "balance", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["result"] == "0" + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a valid address", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 100) + + params = %{ + "module" => "account", + "action" => "balance", + "address" => "#{address.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["result"] == "#{address.fetched_coin_balance.value}" + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with multiple valid addresses", %{conn: conn} do + addresses = + for _ <- 1..2 do + insert(:address, fetched_coin_balance: Enum.random(1..1_000)) + end + + address_param = + addresses + |> Enum.map(&"#{&1.hash}") + |> Enum.join(",") + + params = %{ + "module" => "account", + "action" => "balance", + "address" => address_param + } + + expected_result = + Enum.map(addresses, fn address -> + %{"account" => "#{address.hash}", "balance" => "#{address.fetched_coin_balance.value}", "stale" => false} + end) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "supports GET and POST requests", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 100) + + params = %{ + "module" => "account", + "action" => "balance", + "address" => "#{address.hash}" + } + + assert get_response = + conn + |> get("/api", params) + |> json_response(200) + + assert post_response = + conn + |> post("/api", params) + |> json_response(200) + + assert get_response == post_response + end + end + + describe "balancemulti" do + test "with an invalid and a valid address hash", %{conn: conn} do + address1 = "invalidhash" + address2 = "0x9bf49d5875030175f3d5d4a67631a87ab4df526b" + + params = %{ + "module" => "account", + "action" => "balancemulti", + "address" => "#{address1},#{address2}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["message"] =~ "Invalid address hash" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with multiple addresses that don't exist", %{conn: conn} do + address1 = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + address2 = "0x9bf49d5875030175f3d5d4a67631a87ab4df526b" + + params = %{ + "module" => "account", + "action" => "balancemulti", + "address" => "#{address1},#{address2}" + } + + expected_result = [ + %{"account" => address1, "balance" => "0", "stale" => false}, + %{"account" => address2, "balance" => "0", "stale" => false} + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert :ok = ExJsonSchema.Validator.validate(schema, response) + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with multiple valid addresses", %{conn: conn} do + addresses = + for _ <- 1..4 do + insert(:address, fetched_coin_balance: Enum.random(1..1_000)) + end + + address_param = + addresses + |> Enum.map(&"#{&1.hash}") + |> Enum.join(",") + + params = %{ + "module" => "account", + "action" => "balancemulti", + "address" => address_param + } + + expected_result = + Enum.map(addresses, fn address -> + %{"account" => "#{address.hash}", "balance" => "#{address.fetched_coin_balance.value}", "stale" => false} + end) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with an address that exists and one that doesn't", %{conn: conn} do + address1 = insert(:address, fetched_coin_balance: 100) + address2_hash = "0x9bf49d5875030175f3d5d4a67631a87ab4df526b" + + params = %{ + "module" => "account", + "action" => "balancemulti", + "address" => "#{address1.hash},#{address2_hash}" + } + + expected_result = [ + %{"account" => address2_hash, "balance" => "0", "stale" => false}, + %{"account" => "#{address1.hash}", "balance" => "#{address1.fetched_coin_balance.value}", "stale" => false} + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "up to a maximum of 20 addresses in a single request", %{conn: conn} do + addresses = insert_list(25, :address, fetched_coin_balance: 0) + + address_param = + addresses + |> Enum.map(&"#{&1.hash}") + |> Enum.join(",") + + params = %{ + "module" => "account", + "action" => "balancemulti", + "address" => address_param + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 20 + assert response["status"] == "1" + assert response["message"] == "OK" + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with a single address", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 100) + + params = %{ + "module" => "account", + "action" => "balancemulti", + "address" => "#{address.hash}" + } + + expected_result = [ + %{"account" => "#{address.hash}", "balance" => "#{address.fetched_coin_balance.value}", "stale" => false} + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + + schema = balance_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "supports GET and POST requests", %{conn: conn} do + addresses = + for _ <- 1..4 do + insert(:address, fetched_coin_balance: Enum.random(1..1_000)) + end + + address_param = + addresses + |> Enum.map(&"#{&1.hash}") + |> Enum.join(",") + + params = %{ + "module" => "account", + "action" => "balancemulti", + "address" => address_param + } + + assert get_response = + conn + |> get("/api", params) + |> json_response(200) + + assert post_response = + conn + |> post("/api", params) + |> json_response(200) + + assert get_response == post_response + end + end + + describe "txlist" do + test "with missing address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlist" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "'address' is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No transactions found" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with a valid address", %{conn: conn} do + address = insert(:address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert(from_address: address) + |> with_block(status: :ok) + + # ^ 'status: :ok' means `isError` in response should be '0' + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}" + } + + expected_result = [ + %{ + "blockNumber" => "#{transaction.block_number}", + "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", + "hash" => "#{transaction.hash}", + "nonce" => "#{transaction.nonce}", + "blockHash" => "#{block.hash}", + "transactionIndex" => "#{transaction.index}", + "from" => "#{transaction.from_address_hash}", + "to" => "#{transaction.to_address_hash}", + "value" => "#{transaction.value.value}", + "gas" => "#{transaction.gas}", + "gasPrice" => "#{transaction.gas_price.value}", + "isError" => "0", + "txreceipt_status" => "1", + "input" => "#{transaction.input}", + "contractAddress" => "#{transaction.created_contract_address_hash}", + "cumulativeGasUsed" => "#{transaction.cumulative_gas_used}", + "gasUsed" => "#{transaction.gas_used}", + "confirmations" => "0" + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "includes correct confirmations value", %{conn: conn} do + insert(:block) + address = insert(:address) + + transaction = + %Transaction{hash: hash} = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}" + } + + block_height = Chain.block_height() + expected_confirmations = block_height - transaction.block_number + + assert %{"result" => [returned_transaction]} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert returned_transaction["confirmations"] == "#{expected_confirmations}" + assert returned_transaction["hash"] == "#{hash}" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "returns '1' for 'isError' with failed transaction", %{conn: conn} do + address = insert(:address) + + %Transaction{hash: hash} = + :transaction + |> insert(from_address: address) + |> with_block(status: :error) + + # ^ 'status: :error' means `isError` in response should be '1' + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}" + } + + assert %{"result" => [returned_transaction]} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert returned_transaction["isError"] == "1" + assert returned_transaction["txreceipt_status"] == "0" + assert returned_transaction["hash"] == "#{hash}" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with address with multiple transactions", %{conn: conn} do + address1 = insert(:address) + address2 = insert(:address) + + transactions = + 3 + |> insert_list(:transaction, from_address: address1) + |> with_block() + + :transaction + |> insert(from_address: address2) + |> with_block() + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address1.hash}" + } + + expected_transaction_hashes = Enum.map(transactions, &"#{&1.hash}") + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 3 + + for returned_transaction <- response["result"] do + assert returned_transaction["hash"] in expected_transaction_hashes + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "orders transactions by block, in ascending order", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + third_block = insert(:block) + address = insert(:address) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(second_block) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(third_block) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(first_block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "sort" => "asc" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + block_numbers_order = + Enum.map(response["result"], fn transaction -> + String.to_integer(transaction["blockNumber"]) + end) + + assert block_numbers_order == Enum.sort(block_numbers_order, &(&1 <= &2)) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "orders transactions by block, in descending order", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + third_block = insert(:block) + address = insert(:address) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(second_block) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(third_block) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(first_block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "sort" => "desc" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + block_numbers_order = + Enum.map(response["result"], fn transaction -> + String.to_integer(transaction["blockNumber"]) + end) + + assert block_numbers_order == Enum.sort(block_numbers_order, &(&1 >= &2)) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores invalid sort option, defaults to ascending", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + third_block = insert(:block) + address = insert(:address) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(second_block) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(third_block) + + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(first_block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "sort" => "invalidsortoption" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + block_numbers_order = + Enum.map(response["result"], fn transaction -> + String.to_integer(transaction["blockNumber"]) + end) + + assert block_numbers_order == Enum.sort(block_numbers_order, &(&1 >= &2)) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with valid pagination params", %{conn: conn} do + # To get paginated results on this endpoint Etherscan's docs say: + # + # "(To get paginated results use page= and offset=)" + + first_block = insert(:block) + second_block = insert(:block) + third_block = insert(:block) + address = insert(:address) + + _second_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(second_block) + + first_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(third_block) + + _third_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(first_block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + # page number + "page" => "1", + # page size + "offset" => "2" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + page1_hashes = Enum.map(response["result"], & &1["hash"]) + + assert length(response["result"]) == 2 + + for transaction <- first_block_transactions do + assert "#{transaction.hash}" in page1_hashes + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores pagination params when invalid", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + third_block = insert(:block) + address = insert(:address) + + _second_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(second_block) + + _third_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(third_block) + + _first_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(first_block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + # page number + "page" => "invalidpage", + # page size + "offset" => "invalidoffset" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 6 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores offset param if offset is less than 1", %{conn: conn} do + address = insert(:address) + + 6 + |> insert_list(:transaction, from_address: address) + |> with_block() + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + # page number + "page" => "1", + # page size + "offset" => "0" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 6 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores offset param if offset is over 10,000", %{conn: conn} do + address = insert(:address) + + 6 + |> insert_list(:transaction, from_address: address) + |> with_block() + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + # page number + "page" => "1", + # page size + "offset" => "10_500" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 6 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with page number with no results", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + third_block = insert(:block) + address = insert(:address) + + _second_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(second_block) + + _third_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(third_block) + + _first_block_transactions = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(first_block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + # page number + "page" => "5", + # page size + "offset" => "2" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No transactions found" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with start_block and end_block params", %{conn: conn} do + blocks = [_, second_block, third_block, _] = insert_list(4, :block) + address = insert(:address) + + for block <- blocks do + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + end + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "start_block" => "#{second_block.number}", + "end_block" => "#{third_block.number}" + } + + expected_block_numbers = [ + "#{second_block.number}", + "#{third_block.number}" + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 4 + + for transaction <- response["result"] do + assert transaction["blockNumber"] in expected_block_numbers + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with start_block but without end_block", %{conn: conn} do + blocks = [_, _, third_block, fourth_block] = insert_list(4, :block) + address = insert(:address) + + for block <- blocks do + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + end + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "start_block" => "#{third_block.number}" + } + + expected_block_numbers = [ + "#{third_block.number}", + "#{fourth_block.number}" + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 4 + + for transaction <- response["result"] do + assert transaction["blockNumber"] in expected_block_numbers + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with end_block but without start_block", %{conn: conn} do + blocks = [first_block, second_block, _, _] = insert_list(4, :block) + address = insert(:address) + + for block <- blocks do + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + end + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "end_block" => "#{second_block.number}" + } + + expected_block_numbers = [ + "#{first_block.number}", + "#{second_block.number}" + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 4 + + for transaction <- response["result"] do + assert transaction["blockNumber"] in expected_block_numbers + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores invalid start_block and end_block", %{conn: conn} do + blocks = [_, _, _, _] = insert_list(4, :block) + address = insert(:address) + + for block <- blocks do + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + end + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "start_block" => "invalidstart", + "end_block" => "invalidend" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 8 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + 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) + timestamp3 = Timex.shift(now, hours: -1) + blocks1 = insert_list(2, :block, timestamp: timestamp1) + blocks2 = [third_block, fourth_block] = insert_list(2, :block, timestamp: timestamp2) + blocks3 = insert_list(2, :block, timestamp: timestamp3) + address = insert(:address) + + for block <- Enum.concat([blocks1, blocks2, blocks3]) do + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + end + + start_timestamp = now |> Timex.shift(hours: -4) |> Timex.to_unix() + end_timestamp = now |> Timex.shift(hours: -2) |> Timex.to_unix() + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "start_timestamp" => "#{start_timestamp}", + "end_timestamp" => "#{end_timestamp}" + } + + expected_block_numbers = [ + "#{third_block.number}", + "#{fourth_block.number}" + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 4 + + for transaction <- response["result"] do + assert transaction["blockNumber"] in expected_block_numbers + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + 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) + timestamp3 = Timex.shift(now, hours: -1) + blocks1 = insert_list(2, :block, timestamp: timestamp1) + blocks2 = [third_block, fourth_block] = insert_list(2, :block, timestamp: timestamp2) + blocks3 = [fifth_block, sixth_block] = insert_list(2, :block, timestamp: timestamp3) + address = insert(:address) + + for block <- Enum.concat([blocks1, blocks2, blocks3]) do + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + end + + start_timestamp = now |> Timex.shift(hours: -4) |> Timex.to_unix() + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "start_timestamp" => "#{start_timestamp}" + } + + expected_block_numbers = [ + "#{third_block.number}", + "#{fourth_block.number}", + "#{fifth_block.number}", + "#{sixth_block.number}" + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 8 + + for transaction <- response["result"] do + assert transaction["blockNumber"] in expected_block_numbers + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + 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) + timestamp3 = Timex.shift(now, hours: -1) + blocks1 = [first_block, second_block] = insert_list(2, :block, timestamp: timestamp1) + blocks2 = insert_list(2, :block, timestamp: timestamp2) + blocks3 = insert_list(2, :block, timestamp: timestamp3) + address = insert(:address) + + for block <- Enum.concat([blocks1, blocks2, blocks3]) do + 2 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + end + + end_timestamp = now |> Timex.shift(hours: -5) |> Timex.to_unix() + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "end_timestamp" => "#{end_timestamp}" + } + + expected_block_numbers = [ + "#{first_block.number}", + "#{second_block.number}" + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 4 + + for transaction <- response["result"] do + assert transaction["blockNumber"] in expected_block_numbers + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with filter_by=to option", %{conn: conn} do + block = insert(:block) + address = insert(:address) + + insert(:transaction, from_address: address) + |> with_block(block) + + insert(:transaction, to_address: address) + |> with_block(block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "filter_by" => "to" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 1 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with filter_by=from option", %{conn: conn} do + block = insert(:block) + address = insert(:address) + + insert(:transaction, from_address: address) + |> with_block(block) + + insert(:transaction, from_address: address) + |> with_block(block) + + insert(:transaction, to_address: address) + |> with_block(block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "filter_by" => "from" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 2 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "supports GET and POST requests", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}" + } + + assert get_response = + conn + |> get("/api", params) + |> json_response(200) + + assert post_response = + conn + |> post("/api", params) + |> json_response(200) + + assert get_response == post_response + end + end + + describe "pendingtxlist" do + test "with missing address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "pendingtxlist" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "'address' is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No transactions found" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with a valid address", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address.hash}" + } + + expected_result = [ + %{ + "hash" => "#{transaction.hash}", + "nonce" => "#{transaction.nonce}", + "from" => "#{transaction.from_address_hash}", + "to" => "#{transaction.to_address_hash}", + "value" => "#{transaction.value.value}", + "gas" => "#{transaction.gas}", + "gasPrice" => "#{transaction.gas_price.value}", + "input" => "#{transaction.input}", + "contractAddress" => "#{transaction.created_contract_address_hash}", + "cumulativeGasUsed" => "#{transaction.cumulative_gas_used}", + "gasUsed" => "#{transaction.gas_used}" + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with address with multiple transactions", %{conn: conn} do + address1 = insert(:address) + address2 = insert(:address) + + transactions = + 3 + |> insert_list(:transaction, from_address: address1) + + :transaction + |> insert(from_address: address2) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address1.hash}" + } + + expected_transaction_hashes = Enum.map(transactions, &"#{&1.hash}") + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 3 + + for returned_transaction <- response["result"] do + assert returned_transaction["hash"] in expected_transaction_hashes + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with valid pagination params", %{conn: conn} do + address = insert(:address) + + _transactions_1 = + 2 + |> insert_list(:transaction, from_address: address) + + _transactions_2 = + 2 + |> insert_list(:transaction, from_address: address) + + transactions_3 = + 2 + |> insert_list(:transaction, from_address: address) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address.hash}", + # page number + "page" => "1", + # page size + "offset" => "2" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + page1_hashes = Enum.map(response["result"], & &1["hash"]) + + assert length(response["result"]) == 2 + + for transaction <- transactions_3 do + assert "#{transaction.hash}" in page1_hashes + end + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores pagination params when invalid", %{conn: conn} do + address = insert(:address) + + _transactions_1 = + 2 + |> insert_list(:transaction, from_address: address) + + _transactions_2 = + 2 + |> insert_list(:transaction, from_address: address) + + _transactions_3 = + 2 + |> insert_list(:transaction, from_address: address) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address.hash}", + # page number + "page" => "invalidpage", + # page size + "offset" => "invalidoffset" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 6 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores offset param if offset is less than 1", %{conn: conn} do + address = insert(:address) + + 6 + |> insert_list(:transaction, from_address: address) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address.hash}", + # page number + "page" => "1", + # page size + "offset" => "0" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 6 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "ignores offset param if offset is over 10,000", %{conn: conn} do + address = insert(:address) + + 6 + |> insert_list(:transaction, from_address: address) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address.hash}", + # page number + "page" => "1", + # page size + "offset" => "10_500" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 6 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "with page number with no results", %{conn: conn} do + address = insert(:address) + + _transactions_1 = + 2 + |> insert_list(:transaction, from_address: address) + + _transactions_2 = + 2 + |> insert_list(:transaction, from_address: address) + + _transactions_3 = + 2 + |> insert_list(:transaction, from_address: address) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address.hash}", + # page number + "page" => "5", + # page size + "offset" => "2" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No transactions found" + assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) + end + + test "supports GET and POST requests", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + + params = %{ + "module" => "account", + "action" => "pendingtxlist", + "address" => "#{address.hash}" + } + + assert get_response = + conn + |> get("/api", params) + |> json_response(200) + + assert post_response = + conn + |> post("/api", params) + |> json_response(200) + + assert get_response == post_response + end + end + + describe "txlistinternal" do + test "with missing txhash and address", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlistinternal" + } + + response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "txhash or address is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + end + + describe "txlistinternal with txhash" do + test "with an invalid txhash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlistinternal", + "txhash" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid txhash format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "with a txhash that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlistinternal", + "txhash" => "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No internal transactions found" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "response includes all the expected fields", %{conn: conn} do + address = insert(:address) + contract_address = insert(:contract_address) + + block = insert(:block) + + transaction = + :transaction + |> insert(from_address: address, to_address: nil) + |> with_contract_creation(contract_address) + |> with_block(block) + + internal_transaction = + :internal_transaction_create + |> insert( + transaction: transaction, + index: 0, + from_address: address, + block_hash: transaction.block_hash, + block_index: 0 + ) + |> with_contract_creation(contract_address) + + params = %{ + "module" => "account", + "action" => "txlistinternal", + "txhash" => "#{transaction.hash}" + } + + expected_result = [ + %{ + "blockNumber" => "#{transaction.block_number}", + "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", + "from" => "#{internal_transaction.from_address_hash}", + "to" => "#{internal_transaction.to_address_hash}", + "value" => "#{internal_transaction.value.value}", + "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}", + "transactionHash" => "#{transaction.hash}", + "isError" => "0", + "errCode" => "#{internal_transaction.error}" + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "isError is true if internal transaction has an error", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + internal_transaction_details = [ + transaction: transaction, + index: 0, + type: :reward, + error: "some error", + block_hash: transaction.block_hash, + block_index: 0 + ] + + insert(:internal_transaction_create, internal_transaction_details) + + params = %{ + "module" => "account", + "action" => "txlistinternal", + "txhash" => "#{transaction.hash}" + } + + assert %{"result" => [found_internal_transaction]} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert found_internal_transaction["isError"] == "1" + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "with transaction with multiple internal transactions", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + for index <- 0..2 do + insert(:internal_transaction_create, + transaction: transaction, + index: index, + block_hash: transaction.block_hash, + block_index: index + ) + end + + params = %{ + "module" => "account", + "action" => "txlistinternal", + "txhash" => "#{transaction.hash}" + } + + assert %{"result" => found_internal_transactions} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(found_internal_transactions) == 3 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + end + + describe "txlistinternal with address" do + test "with an invalid address", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlistinternal", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "with a address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "txlistinternal", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No internal transactions found" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "response includes all the expected fields", %{conn: conn} do + address = insert(:address) + contract_address = insert(:contract_address) + + block = insert(:block) + + transaction = + :transaction + |> insert(from_address: address, to_address: nil) + |> with_contract_creation(contract_address) + |> with_block(block) + + internal_transaction = + :internal_transaction_create + |> insert( + transaction: transaction, + index: 0, + from_address: address, + block_number: block.number, + block_hash: transaction.block_hash, + block_index: 0 + ) + |> with_contract_creation(contract_address) + + params = %{ + "module" => "account", + "action" => "txlistinternal", + "address" => "#{address.hash}" + } + + expected_result = [ + %{ + "blockNumber" => "#{transaction.block_number}", + "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", + "from" => "#{internal_transaction.from_address_hash}", + "to" => "#{internal_transaction.to_address_hash}", + "value" => "#{internal_transaction.value.value}", + "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", + "index" => "#{internal_transaction.index}", + "transactionHash" => "#{transaction.hash}", + "errCode" => "#{internal_transaction.error}" + } + ] + + response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "isError is true if internal transaction has an error", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + internal_transaction_details = [ + from_address: address, + transaction: transaction, + index: 0, + type: :reward, + error: "some error", + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0 + ] + + insert(:internal_transaction_create, internal_transaction_details) + + params = %{ + "module" => "account", + "action" => "txlistinternal", + "address" => "#{address.hash}" + } + + assert %{"result" => [found_internal_transaction]} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert found_internal_transaction["isError"] == "1" + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + + test "with transaction with multiple internal transactions", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + for index <- 0..2 do + internal_transaction_details = %{ + from_address: address, + transaction: transaction, + index: index, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: index + } + + insert(:internal_transaction_create, internal_transaction_details) + end + + params = %{ + "module" => "account", + "action" => "txlistinternal", + "address" => "#{address.hash}" + } + + assert %{"result" => found_internal_transactions} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(found_internal_transactions) == 3 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(txlistinternal_schema(), response) + end + end + + describe "tokentx" do + test "with missing address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokentx" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "address is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokentx", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokentx", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No token transfers found" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "has correct value for ERC-721", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + token_address = insert(:contract_address) + insert(:token, %{contract_address: token_address, type: "ERC-721"}) + + token_transfer = + insert(:token_transfer, %{ + token_contract_address: token_address, + token_id: 666, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + }) + + {:ok, _} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash) + + params = %{ + "module" => "account", + "action" => "tokentx", + "address" => to_string(token_transfer.from_address.hash) + } + + assert response = + %{"result" => [result]} = + conn + |> get("/api", params) + |> json_response(200) + + assert result["tokenID"] == to_string(token_transfer.token_id) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "returns all the required fields", %{conn: conn} do + transaction = + %{block: block} = + :transaction + |> insert() + |> with_block() + + token_transfer = + insert(:token_transfer, block: transaction.block, transaction: transaction, block_number: block.number) + + {:ok, token} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash) + + params = %{ + "module" => "account", + "action" => "tokentx", + "address" => to_string(token_transfer.from_address.hash) + } + + expected_result = [ + %{ + "blockNumber" => to_string(transaction.block_number), + "timeStamp" => to_string(DateTime.to_unix(block.timestamp)), + "hash" => to_string(token_transfer.transaction_hash), + "nonce" => to_string(transaction.nonce), + "blockHash" => to_string(block.hash), + "from" => to_string(token_transfer.from_address_hash), + "contractAddress" => to_string(token_transfer.token_contract_address_hash), + "to" => to_string(token_transfer.to_address_hash), + "value" => to_string(token_transfer.amount), + "tokenName" => token.name, + "tokenSymbol" => token.symbol, + "tokenDecimal" => to_string(token.decimals), + "transactionIndex" => to_string(transaction.index), + "gas" => to_string(transaction.gas), + "gasPrice" => to_string(transaction.gas_price.value), + "gasUsed" => to_string(transaction.gas_used), + "cumulativeGasUsed" => to_string(transaction.cumulative_gas_used), + "logIndex" => to_string(token_transfer.log_index), + "input" => to_string(transaction.input), + "confirmations" => "0" + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "with an invalid contract address", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokentx", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + "contractaddress" => "invalid" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid contract address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "filters results by contract address", %{conn: conn} do + address = insert(:address) + + contract_address = insert(:contract_address) + + insert(:token, contract_address: contract_address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:token_transfer, + from_address: address, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + insert(:token_transfer, + from_address: address, + token_contract_address: contract_address, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + params = %{ + "module" => "account", + "action" => "tokentx", + "address" => to_string(address.hash), + "contractaddress" => to_string(contract_address.hash) + } + + assert response = + %{"result" => [result]} = + conn + |> get("/api", params) + |> json_response(200) + + assert result["contractAddress"] == to_string(contract_address.hash) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + end + + describe "tokenbalance" do + test "without required params", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenbalance" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "missing: address, contractaddress" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + + test "with contract address but without address", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenbalance", + "contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "missing: address" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + + test "with address but without contract address", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenbalance", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "missing: contractaddress" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + + test "with an invalid contract address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenbalance", + "contractaddress" => "badhash", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid contractaddress format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenbalance", + "contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + + test "with a contract address and address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenbalance", + "contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + "address" => "0x9bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == "0" + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + + test "with contract address and address without row in token_balances table", %{conn: conn} do + token = insert(:token) + address = insert(:address) + + params = %{ + "module" => "account", + "action" => "tokenbalance", + "contractaddress" => to_string(token.contract_address_hash), + "address" => to_string(address.hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == "0" + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + + test "with contract address and address with existing balance in token_balances table", %{conn: conn} do + current_token_balance = insert(:address_current_token_balance) + + params = %{ + "module" => "account", + "action" => "tokenbalance", + "contractaddress" => to_string(current_token_balance.token_contract_address_hash), + "address" => to_string(current_token_balance.address_hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == to_string(current_token_balance.value) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokenbalance_schema(), response) + end + end + + describe "tokenlist" do + test "without address param", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenlist" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "address is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokenlist_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenlist", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokenlist_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "tokenlist", + "address" => "0x9bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No tokens found" + assert :ok = ExJsonSchema.Validator.validate(tokenlist_schema(), response) + end + + test "with an address without row in token_balances table", %{conn: conn} do + address = insert(:address) + + params = %{ + "module" => "account", + "action" => "tokenlist", + "address" => to_string(address.hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No tokens found" + assert :ok = ExJsonSchema.Validator.validate(tokenlist_schema(), response) + end + + test "with address with existing balance in token_balances table", %{conn: conn} do + token_balance = :address_current_token_balance |> insert() |> Repo.preload(:token) + + params = %{ + "module" => "account", + "action" => "tokenlist", + "address" => to_string(token_balance.address_hash) + } + + expected_result = [ + %{ + "balance" => to_string(token_balance.value), + "contractAddress" => to_string(token_balance.token_contract_address_hash), + "name" => token_balance.token.name, + "decimals" => to_string(token_balance.token.decimals), + "symbol" => token_balance.token.symbol, + "type" => token_balance.token.type + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokenlist_schema(), response) + end + + test "with address with multiple tokens", %{conn: conn} do + address = insert(:address) + other_address = insert(:address) + insert(:address_current_token_balance, address: address) + insert(:address_current_token_balance, address: address) + insert(:address_current_token_balance, address: other_address) + + params = %{ + "module" => "account", + "action" => "tokenlist", + "address" => to_string(address.hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 2 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokenlist_schema(), response) + end + end + + describe "getminedblocks" do + test "with missing address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "getminedblocks" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "'address' is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(block_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "getminedblocks", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(block_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "account", + "action" => "getminedblocks", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No blocks found" + assert :ok = ExJsonSchema.Validator.validate(block_schema(), response) + end + + test "returns all the required fields", %{conn: conn} do + %{block_range: range} = insert(:emission_reward) + + block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) + + :transaction + |> insert(gas_price: 1) + |> with_block(block, gas_used: 1) + + expected_result = [ + %{ + "blockNumber" => to_string(block.number), + "timeStamp" => to_string(block.timestamp) + } + ] + + params = %{ + "module" => "account", + "action" => "getminedblocks", + "address" => to_string(block.miner_hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(block_schema(), response) + end + + test "with a block with one transaction", %{conn: conn} do + %{block_range: range} = insert(:emission_reward) + + block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) + + :transaction + |> insert(gas_price: 1) + |> with_block(block, gas_used: 1) + + params = %{ + "module" => "account", + "action" => "getminedblocks", + "address" => to_string(block.miner_hash) + } + + expected_result = [ + %{ + "blockNumber" => to_string(block.number), + "timeStamp" => to_string(block.timestamp) + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(block_schema(), response) + end + + test "with pagination options", %{conn: conn} do + %{block_range: range} = insert(:emission_reward) + + block_numbers = Range.new(range.from, range.to) + + [block_number1, block_number2] = Enum.take(block_numbers, 2) + + address = insert(:address) + + _block1 = insert(:block, number: block_number1, miner: address) + block2 = insert(:block, number: block_number2, miner: address) + + :transaction + |> insert(gas_price: 2) + |> with_block(block2, gas_used: 2) + + params = %{ + "module" => "account", + "action" => "getminedblocks", + "address" => to_string(address.hash), + # page number + "page" => "1", + # page size + "offset" => "1" + } + + expected_result = [ + %{ + "blockNumber" => to_string(block2.number), + "timeStamp" => to_string(block2.timestamp) + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(block_schema(), response) + end + end + + describe "optional_params/1" do + test "includes valid optional params in the required format" do + params = %{ + "start_block" => "100", + "end_block" => "120", + "sort" => "asc", + # page number + "page" => "1", + # page size + "offset" => "2", + "filter_by" => "to", + "start_timestamp" => "1539186474", + "end_timestamp" => "1539186474" + } + + optional_params = AddressController.optional_params(params) + + # 1539186474 equals "2018-10-10 15:47:54Z" + {:ok, expected_timestamp, _} = DateTime.from_iso8601("2018-10-10 15:47:54Z") + + assert optional_params.page_number == 1 + assert optional_params.page_size == 2 + assert optional_params.order_by_direction == :asc + assert optional_params.start_block == 100 + assert optional_params.end_block == 120 + assert optional_params.filter_by == "to" + assert optional_params.start_timestamp == expected_timestamp + assert optional_params.end_timestamp == expected_timestamp + end + + test "'sort' values can be 'asc' or 'desc'" do + params1 = %{"sort" => "asc"} + + optional_params = AddressController.optional_params(params1) + + assert optional_params.order_by_direction == :asc + + params2 = %{"sort" => "desc"} + + optional_params = AddressController.optional_params(params2) + + assert optional_params.order_by_direction == :desc + + params3 = %{"sort" => "invalid"} + + assert AddressController.optional_params(params3) == %{} + end + + 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 = %{"filter_by" => "from"} + + optional_params2 = AddressController.optional_params(params2) + + assert optional_params2.filter_by == "from" + + params3 = %{"filter_by" => "invalid"} + + assert AddressController.optional_params(params3) == %{} + end + + test "only includes optional params when they're given" do + assert AddressController.optional_params(%{}) == %{} + end + + test "ignores invalid optional params, keeps valid ones" do + params1 = %{ + "start_block" => "invalid", + "end_block" => "invalid", + "sort" => "invalid", + "page" => "invalid", + "offset" => "invalid", + "start_timestamp" => "invalid", + "end_timestamp" => "invalid" + } + + assert AddressController.optional_params(params1) == %{} + + params2 = %{ + "start_block" => "4", + "end_block" => "10", + "sort" => "invalid", + "page" => "invalid", + "offset" => "invalid", + "start_timestamp" => "invalid", + "end_timestamp" => "invalid" + } + + optional_params = AddressController.optional_params(params2) + + assert optional_params.start_block == 4 + assert optional_params.end_block == 10 + end + + test "ignores 'page' if less than 1" do + params = %{"page" => "0"} + + assert AddressController.optional_params(params) == %{} + end + + test "ignores 'offset' if less than 1" do + params = %{"offset" => "0"} + + assert AddressController.optional_params(params) == %{} + end + + test "ignores 'offset' if more than 10,000" do + params = %{"offset" => "10001"} + + assert AddressController.optional_params(params) == %{} + end + end + + describe "fetch_required_params/2" do + test "returns error with missing param" do + params = %{"address" => "some address"} + + required_params = ~w(address contractaddress) + + result = AddressController.fetch_required_params(params, required_params) + + assert result == {:required_params, {:error, ["contractaddress"]}} + end + + test "returns ok with all required params" do + params = %{"address" => "some address", "contractaddress" => "some contract"} + + required_params = ~w(address contractaddress) + + result = AddressController.fetch_required_params(params, required_params) + + assert result == {:required_params, {:ok, params}} + end + end + + defp listaccounts_schema do + resolve_schema(%{ + "type" => "array", + "items" => %{ + "type" => "object", + "properties" => %{ + "address" => %{"type" => "string"}, + "balance" => %{"type" => "string"}, + "stale" => %{"type" => "boolean"} + } + } + }) + end + + defp balance_schema do + resolve_schema(%{ + "type" => ["string", "null", "array"], + "items" => %{ + "type" => "object", + "properties" => %{ + "account" => %{"type" => "string"}, + "balance" => %{"type" => "string"}, + "stale" => %{"type" => "boolean"} + } + } + }) + end + + defp txlist_schema do + resolve_schema(%{ + "type" => ["null", "array"], + "items" => %{ + "type" => "object", + "properties" => %{ + "blockNumber" => %{"type" => "string"}, + "timeStamp" => %{"type" => "string"}, + "hash" => %{"type" => "string"}, + "nonce" => %{"type" => "string"}, + "blockHash" => %{"type" => "string"}, + "transactionIndex" => %{"type" => "string"}, + "from" => %{"type" => "string"}, + "to" => %{"type" => "string"}, + "value" => %{"type" => "string"}, + "gas" => %{"type" => "string"}, + "gasPrice" => %{"type" => "string"}, + "isError" => %{"type" => "string"}, + "txreceipt_status" => %{"type" => "string"}, + "input" => %{"type" => "string"}, + "contractAddress" => %{"type" => "string"}, + "cumulativeGasUsed" => %{"type" => "string"}, + "gasUsed" => %{"type" => "string"}, + "confirmations" => %{"type" => "string"} + } + } + }) + end + + defp txlistinternal_schema do + resolve_schema(%{ + "type" => ["array", "null"], + "items" => %{ + "type" => "object", + "properties" => %{ + "blockNumber" => %{"type" => "string"}, + "timeStamp" => %{"type" => "string"}, + "from" => %{"type" => "string"}, + "to" => %{"type" => "string"}, + "value" => %{"type" => "string"}, + "contractAddress" => %{"type" => "string"}, + "transactionHash" => %{"type" => "string"}, + "index" => %{"type" => "string"}, + "input" => %{"type" => "string"}, + "type" => %{"type" => "string"}, + "gas" => %{"type" => "string"}, + "gasUsed" => %{"type" => "string"}, + "isError" => %{"type" => "string"}, + "errCode" => %{"type" => "string"} + } + } + }) + end + + defp tokentx_schema do + resolve_schema(%{ + "type" => ["array", "null"], + "items" => %{ + "type" => "object", + "properties" => %{ + "blockNumber" => %{"type" => "string"}, + "timeStamp" => %{"type" => "string"}, + "hash" => %{"type" => "string"}, + "nonce" => %{"type" => "string"}, + "blockHash" => %{"type" => "string"}, + "from" => %{"type" => "string"}, + "contractAddress" => %{"type" => "string"}, + "to" => %{"type" => "string"}, + "logIndex" => %{"type" => "string"}, + "value" => %{"type" => "string"}, + "tokenName" => %{"type" => "string"}, + "tokenID" => %{"type" => "string"}, + "tokenSymbol" => %{"type" => "string"}, + "tokenDecimal" => %{"type" => "string"}, + "transactionIndex" => %{"type" => "string"}, + "gas" => %{"type" => "string"}, + "gasPrice" => %{"type" => "string"}, + "gasUsed" => %{"type" => "string"}, + "cumulativeGasUsed" => %{"type" => "string"}, + "input" => %{"type" => "string"}, + "confirmations" => %{"type" => "string"} + } + } + }) + end + + defp tokenbalance_schema, do: resolve_schema(%{"type" => ["string", "null"]}) + + defp tokenlist_schema do + resolve_schema(%{ + "type" => ["array", "null"], + "items" => %{ + "type" => "object", + "properties" => %{ + "balance" => %{"type" => "string"}, + "contractAddress" => %{"type" => "string"}, + "name" => %{"type" => "string"}, + "decimals" => %{"type" => "string"}, + "symbol" => %{"type" => "string"}, + "type" => %{"type" => "string"} + } + } + }) + end + + defp block_schema do + resolve_schema(%{ + "type" => ["array", "null"], + "items" => %{ + "type" => "object", + "properties" => %{ + "blockNumber" => %{"type" => "string"}, + "timeStamp" => %{"type" => "string"}, + "blockReward" => %{"type" => "string"} + } + } + }) + end + + defp resolve_schema(result) do + %{ + "type" => "object", + "properties" => %{ + "message" => %{"type" => "string"}, + "status" => %{"type" => "string"} + } + } + |> put_in(["properties", "result"], result) + |> ExJsonSchema.Schema.resolve() + end + + defp eth_block_number_fake_response(block_quantity) do + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "author" => "0x0000000000000000000000000000000000000000", + "difficulty" => "0x20000", + "extraData" => "0x", + "gasLimit" => "0x663be0", + "gasUsed" => "0x0", + "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + "logsBloom" => + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner" => "0x0000000000000000000000000000000000000000", + "number" => block_quantity, + "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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs new file mode 100644 index 0000000..70e9726 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs @@ -0,0 +1,238 @@ +defmodule BlockScoutWeb.API.RPC.BlockControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.{Hash, Wei} + alias BlockScoutWeb.Chain + + describe "getblockreward" do + test "with missing block number", %{conn: conn} do + response = + conn + |> get("/api", %{"module" => "block", "action" => "getblockreward"}) + |> json_response(200) + + assert response["message"] =~ "'blockno' is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with an invalid block number", %{conn: conn} do + response = + conn + |> get("/api", %{"module" => "block", "action" => "getblockreward", "blockno" => "badnumber"}) + |> json_response(200) + + assert response["message"] =~ "Invalid block number" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with a block that doesn't exist", %{conn: conn} do + response = + conn + |> get("/api", %{"module" => "block", "action" => "getblockreward", "blockno" => "42"}) + |> json_response(200) + + assert response["message"] =~ "Block does not exist" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with a valid block", %{conn: conn} do + %{block_range: range} = emission_reward = insert(:emission_reward) + block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) + + :transaction + |> insert(gas_price: 1) + |> with_block(block, gas_used: 1) + + expected_reward = + emission_reward.reward + |> Wei.to(:wei) + |> Decimal.add(Decimal.new(1)) + |> Decimal.to_string(:normal) + + expected_result = %{ + "blockNumber" => "#{block.number}", + "timeStamp" => DateTime.to_unix(block.timestamp), + "blockMiner" => Hash.to_string(block.miner_hash), + "blockReward" => expected_reward, + "uncles" => nil, + "uncleInclusionReward" => nil + } + + assert response = + conn + |> get("/api", %{"module" => "block", "action" => "getblockreward", "blockno" => "#{block.number}"}) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + end + + describe "getblocknobytime" do + test "with missing timestamp param", %{conn: conn} do + response = + conn + |> get("/api", %{"module" => "block", "action" => "getblocknobytime", "closest" => "after"}) + |> json_response(200) + + assert response["message"] =~ "Query parameter 'timestamp' is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with missing closest param", %{conn: conn} do + response = + conn + |> get("/api", %{"module" => "block", "action" => "getblocknobytime", "timestamp" => "1617019505"}) + |> json_response(200) + + assert response["message"] =~ "Query parameter 'closest' is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with an invalid timestamp param", %{conn: conn} do + response = + conn + |> get("/api", %{ + "module" => "block", + "action" => "getblocknobytime", + "timestamp" => "invalid", + "closest" => " before" + }) + |> json_response(200) + + assert response["message"] =~ "Invalid `timestamp` param" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with an invalid closest param", %{conn: conn} do + response = + conn + |> get("/api", %{ + "module" => "block", + "action" => "getblocknobytime", + "timestamp" => "1617019505", + "closest" => "invalid" + }) + |> json_response(200) + + assert response["message"] =~ "Invalid `closest` param" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with valid params and before", %{conn: conn} do + timestamp_string = "1617020209" + {:ok, timestamp} = Chain.param_to_block_timestamp(timestamp_string) + block = insert(:block, timestamp: timestamp) + + {timestamp_int, _} = Integer.parse(timestamp_string) + + timestamp_in_the_future_str = + (timestamp_int + 1) + |> to_string() + + expected_result = %{ + "blockNumber" => "#{block.number}" + } + + assert response = + conn + |> get("/api", %{ + "module" => "block", + "action" => "getblocknobytime", + "timestamp" => "#{timestamp_in_the_future_str}", + "closest" => "before" + }) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with valid params and after", %{conn: conn} do + timestamp_string = "1617020209" + {:ok, timestamp} = Chain.param_to_block_timestamp(timestamp_string) + block = insert(:block, timestamp: timestamp) + + {timestamp_int, _} = Integer.parse(timestamp_string) + + timestamp_in_the_past_str = + (timestamp_int - 1) + |> to_string() + + expected_result = %{ + "blockNumber" => "#{block.number}" + } + + assert response = + conn + |> get("/api", %{ + "module" => "block", + "action" => "getblocknobytime", + "timestamp" => "#{timestamp_in_the_past_str}", + "closest" => "after" + }) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + schema = resolve_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + end + + defp resolve_schema() do + ExJsonSchema.Schema.resolve(%{ + "type" => "object", + "properties" => %{ + "message" => %{"type" => "string"}, + "status" => %{"type" => "string"}, + "result" => %{ + "type" => ["object", "null"], + "properties" => %{ + "blockNumber" => %{"type" => "string"}, + "timeStamp" => %{"type" => "number"}, + "blockMiner" => %{"type" => "string"}, + "blockReward" => %{"type" => "string"}, + "uncles" => %{"type" => "null"}, + "uncleInclusionReward" => %{"type" => "null"} + } + } + } + }) + end +end 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 new file mode 100644 index 0000000..5c4306b --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -0,0 +1,971 @@ +defmodule BlockScoutWeb.API.RPC.ContractControllerTest do + use BlockScoutWeb.ConnCase + alias Explorer.Chain.SmartContract + alias Explorer.Chain + # alias Explorer.{Chain, Factory} + + 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"}} + end + + test "with an invalid filter value", %{conn: conn, params: params} do + response = + conn + |> get("/api", Map.put(params, "filter", "invalid")) + |> json_response(400) + + assert response["message"] == + "invalid is not a valid value for `filter`. Please use one of: verified, decompiled, unverified, not_decompiled, 1, 2, 3, 4." + + assert response["status"] == "0" + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "with no contracts", %{conn: conn, params: params} do + response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + assert response["result"] == [] + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "with a verified smart contract, all contract information is shown", %{conn: conn, params: params} do + contract = insert(:smart_contract, contract_code_md5: "123") + + response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result(contract)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "with an unverified contract address, only basic information is shown", %{conn: conn, params: params} do + address = insert(:contract_address) + + response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result_not_verified(address.hash)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only unverified contracts shows only unverified contracts", %{params: params, conn: conn} do + address = insert(:contract_address) + insert(:smart_contract, contract_code_md5: "123") + + response = + conn + |> get("/api", Map.put(params, "filter", "unverified")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result_not_verified(address.hash)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only unverified contracts does not show self destructed contracts", %{ + params: params, + conn: conn + } do + address = insert(:contract_address) + insert(:smart_contract, contract_code_md5: "123") + insert(:contract_address, contract_code: "0x") + + response = + conn + |> get("/api", Map.put(params, "filter", "unverified")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result_not_verified(address.hash)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only verified contracts shows only verified contracts", %{params: params, conn: conn} do + insert(:contract_address) + contract = insert(:smart_contract, contract_code_md5: "123") + + response = + conn + |> get("/api", Map.put(params, "filter", "verified")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + 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 + + test "filtering for only decompiled contracts shows only decompiled contracts", %{params: params, conn: conn} do + insert(:contract_address) + decompiled_smart_contract = insert(:decompiled_smart_contract) + + response = + conn + |> get("/api", Map.put(params, "filter", "decompiled")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result_not_verified(decompiled_smart_contract.address_hash)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only decompiled contracts, with a decompiled with version filter", %{params: params, conn: conn} do + insert(:decompiled_smart_contract, decompiler_version: "foobar") + smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz") + + response = + conn + |> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"})) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result_not_verified(smart_contract.address_hash)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only decompiled contracts, with a decompiled with version filter, where another decompiled version exists", + %{params: params, conn: conn} do + non_match = insert(:decompiled_smart_contract, decompiler_version: "foobar") + insert(:decompiled_smart_contract, decompiler_version: "bizbuz", address_hash: non_match.address_hash) + smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz") + + response = + conn + |> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"})) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + 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) + end + + test "filtering for only not_decompiled (and by extension not verified contracts)", %{params: params, conn: conn} do + insert(:decompiled_smart_contract) + insert(:smart_contract, contract_code_md5: "123") + contract_address = insert(:contract_address) + + response = + conn + |> get("/api", Map.put(params, "filter", "not_decompiled")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result_not_verified(contract_address.hash)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only not_decompiled (and by extension not verified contracts) does not show empty contracts", %{ + params: params, + conn: conn + } do + insert(:decompiled_smart_contract) + insert(:smart_contract, contract_code_md5: "123") + insert(:contract_address, contract_code: "0x") + contract_address = insert(:contract_address) + + response = + conn + |> get("/api", Map.put(params, "filter", "not_decompiled")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result_not_verified(contract_address.hash)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + end + + describe "getabi" do + test "with missing address hash", %{conn: conn} do + params = %{ + "module" => "contract", + "action" => "getabi" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "address is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getabi_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "contract", + "action" => "getabi", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address hash" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getabi_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "contract", + "action" => "getabi", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == nil + assert response["status"] == "0" + assert response["message"] == "Contract source code not verified" + assert :ok = ExJsonSchema.Validator.validate(getabi_schema(), response) + end + + test "with a verified contract address", %{conn: conn} do + contract = insert(:smart_contract, contract_code_md5: "123") + + params = %{ + "module" => "contract", + "action" => "getabi", + "address" => to_string(contract.address_hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == Jason.encode!(contract.abi) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getabi_schema(), response) + end + end + + describe "getsourcecode" do + test "with missing address hash", %{conn: conn} do + params = %{ + "module" => "contract", + "action" => "getsourcecode" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "address is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "contract", + "action" => "getsourcecode", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address hash" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "contract", + "action" => "getsourcecode", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + expected_result = [ + %{ + "Address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end + + test "with a verified contract address", %{conn: conn} do + contract = + insert(:smart_contract, + optimization: true, + optimization_runs: 200, + evm_version: "default", + contract_code_md5: "123" + ) + + params = %{ + "module" => "contract", + "action" => "getsourcecode", + "address" => to_string(contract.address_hash) + } + + expected_result = [ + %{ + "Address" => to_string(contract.address_hash), + "SourceCode" => contract.contract_source_code, + "ABI" => Jason.encode!(contract.abi), + "ContractName" => contract.name, + "CompilerVersion" => contract.compiler_version, + # The contract's optimization value is true, so the expected value + # for `OptimizationUsed` is "1". If it was false, the expected value + # would be "0". + "OptimizationUsed" => "true", + "OptimizationRuns" => 200, + "EVMVersion" => "default", + "FileName" => "", + "IsProxy" => "false" + } + ] + + get_implementation() + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end + + test "with constructor arguments", %{conn: conn} do + contract = + insert(:smart_contract, + optimization: true, + optimization_runs: 200, + evm_version: "default", + constructor_arguments: + "00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546", + contract_code_md5: "123" + ) + + params = %{ + "module" => "contract", + "action" => "getsourcecode", + "address" => to_string(contract.address_hash) + } + + expected_result = [ + %{ + "Address" => to_string(contract.address_hash), + "SourceCode" => contract.contract_source_code, + "ABI" => Jason.encode!(contract.abi), + "ContractName" => contract.name, + "CompilerVersion" => contract.compiler_version, + "OptimizationUsed" => "true", + "OptimizationRuns" => 200, + "EVMVersion" => "default", + "ConstructorArguments" => + "00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546", + "FileName" => "", + "IsProxy" => "false" + } + ] + + get_implementation() + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end + + test "with external library", %{conn: conn} do + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + created_contract_address = + insert( + :address, + hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + contract_code: smart_contract_bytecode + ) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :internal_transaction_create, + transaction: transaction, + index: 0, + created_contract_address: created_contract_address, + created_contract_code: smart_contract_bytecode, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + valid_attrs = %{ + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + name: "Test", + compiler_version: "0.4.23", + contract_source_code: + "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ], + optimization: true, + optimization_runs: 200, + evm_version: "default" + } + + external_libraries = [ + %SmartContract.ExternalLibrary{:address_hash => "0xb18aed9518d735482badb4e8b7fd8d2ba425ce95", :name => "Test"}, + %SmartContract.ExternalLibrary{:address_hash => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f", :name => "Test2"} + ] + + {:ok, %SmartContract{} = contract} = Chain.create_smart_contract(valid_attrs, external_libraries) + + params = %{ + "module" => "contract", + "action" => "getsourcecode", + "address" => to_string(contract.address_hash) + } + + expected_result = [ + %{ + "Address" => to_string(contract.address_hash), + "SourceCode" => contract.contract_source_code, + "ABI" => Jason.encode!(contract.abi), + "ContractName" => contract.name, + "CompilerVersion" => contract.compiler_version, + "OptimizationUsed" => "true", + "OptimizationRuns" => 200, + "EVMVersion" => "default", + "ExternalLibraries" => [ + %{"name" => "Test", "address_hash" => "0xb18aed9518d735482badb4e8b7fd8d2ba425ce95"}, + %{"name" => "Test2", "address_hash" => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f"} + ], + "FileName" => "", + "IsProxy" => "false" + } + ] + + get_implementation() + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end + end + + describe "verify" do + test "verify known on sourcify repo contract", %{conn: conn} do + response = verify(conn) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"]["ABI"] == + "[{\"inputs\":[],\"name\":\"retrieve\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_number\",\"type\":\"uint256\"}],\"name\":\"store\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + + assert response["result"]["CompilerVersion"] == "v0.7.6+commit.7338295f" + assert response["result"]["ContractName"] == "Storage" + assert response["result"]["EVMVersion"] == "istanbul" + assert response["result"]["OptimizationUsed"] == "false" + end + + test "verify already verified contract", %{conn: conn} do + _response = verify(conn) + + params = %{ + "module" => "contract", + "action" => "verify_via_sourcify", + "addressHash" => "0x18d89C12e9463Be6343c35C9990361bA4C42AfC2" + } + + response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == "Smart-contract already verified." + assert response["status"] == "0" + assert response["result"] == nil + end + + defp verify(conn) do + smart_contract_bytecode = + "0x6080604052348015600f57600080fd5b506004361060325760003560e01c80632e64cec11460375780636057361d146053575b600080fd5b603d607e565b6040518082815260200191505060405180910390f35b607c60048036036020811015606757600080fd5b81019080803590602001909291905050506087565b005b60008054905090565b806000819055505056fea26469706673582212205afbc4864a2486ec80f10e5eceeaac30e88c9b3dfcd1bfadd6cdf6e6cb6e1fd364736f6c63430007060033" + + _created_contract_address = + insert( + :address, + hash: "0x18d89C12e9463Be6343c35C9990361bA4C42AfC2", + contract_code: smart_contract_bytecode + ) + + params = %{ + "module" => "contract", + "action" => "verify_via_sourcify", + "addressHash" => "0x18d89C12e9463Be6343c35C9990361bA4C42AfC2" + } + + get_implementation() + + conn + |> get("/api", params) + |> json_response(200) + end + + # flaky test + # test "with an address that doesn't exist", %{conn: conn} do + # contract_code_info = Factory.contract_code_info() + + # contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode) + # insert(:transaction, created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input) + + # params = %{ + # "module" => "contract", + # "action" => "verify", + # "addressHash" => to_string(contract_address.hash), + # "name" => contract_code_info.name, + # "compilerVersion" => contract_code_info.version, + # "optimization" => contract_code_info.optimized, + # "contractSourceCode" => contract_code_info.source_code + # } + + # response = + # conn + # |> get("/api", params) + # |> json_response(200) + + # verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash) + + # expected_result = %{ + # "Address" => to_string(contract_address.hash), + # "SourceCode" => + # "/**\n* Submitted for verification at blockscout.com on #{verified_contract.inserted_at}\n*/\n" <> + # contract_code_info.source_code, + # "ABI" => Jason.encode!(contract_code_info.abi), + # "ContractName" => contract_code_info.name, + # "CompilerVersion" => contract_code_info.version, + # "OptimizationUsed" => "false", + # "EVMVersion" => nil + # } + + # assert response["status"] == "1" + # assert response["result"] == expected_result + # assert response["message"] == "OK" + # assert :ok = ExJsonSchema.Validator.validate(verify_schema(), response) + # end + + # flaky test + # test "with external libraries", %{conn: conn} do + # contract_data = + # "#{File.cwd!()}/test/support/fixture/smart_contract/contract_with_lib.json" + # |> File.read!() + # |> Jason.decode!() + # |> List.first() + + # %{ + # "compiler_version" => compiler_version, + # "external_libraries" => external_libraries, + # "name" => name, + # "optimize" => optimize, + # "contract" => contract_source_code, + # "expected_bytecode" => expected_bytecode, + # "tx_input" => tx_input + # } = contract_data + + # contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode) + # insert(:transaction, created_contract_address_hash: contract_address.hash, input: "0x" <> tx_input) + + # params = %{ + # "module" => "contract", + # "action" => "verify", + # "addressHash" => to_string(contract_address.hash), + # "name" => name, + # "compilerVersion" => compiler_version, + # "optimization" => optimize, + # "contractSourceCode" => contract_source_code + # } + + # params_with_external_libraries = + # external_libraries + # |> Enum.with_index() + # |> Enum.reduce(params, fn {{name, address}, index}, acc -> + # name_key = "library#{index + 1}Name" + # address_key = "library#{index + 1}Address" + + # acc + # |> Map.put(name_key, name) + # |> Map.put(address_key, address) + # end) + + # response = + # conn + # |> get("/api", params_with_external_libraries) + # |> json_response(200) + + # assert response["status"] == "1" + # assert response["message"] == "OK" + + # result = response["result"] + + # verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash) + + # assert result["Address"] == to_string(contract_address.hash) + + # assert result["SourceCode"] == + # "/**\n* Submitted for verification at blockscout.com on #{verified_contract.inserted_at}\n*/\n" <> + # contract_source_code + + # assert result["ContractName"] == name + # assert result["DecompiledSourceCode"] == nil + # assert result["DecompilerVersion"] == nil + # assert result["OptimizationUsed"] == "true" + # assert :ok = ExJsonSchema.Validator.validate(verify_schema(), response) + # end + end + + defp listcontracts_schema do + resolve_schema(%{ + "type" => ["array", "null"], + "items" => %{ + "type" => "object", + "properties" => %{ + "Address" => %{"type" => "string"}, + "ABI" => %{"type" => "string"}, + "ContractName" => %{"type" => "string"}, + "CompilerVersion" => %{"type" => "string"}, + "OptimizationUsed" => %{"type" => "string"} + } + } + }) + end + + defp getabi_schema do + resolve_schema(%{ + "type" => ["string", "null"] + }) + end + + defp getsourcecode_schema do + resolve_schema(%{ + "type" => ["array", "null"], + "items" => %{ + "type" => "object", + "properties" => %{ + "Address" => %{"type" => "string"}, + "SourceCode" => %{"type" => "string"}, + "ABI" => %{"type" => "string"}, + "ContractName" => %{"type" => "string"}, + "CompilerVersion" => %{"type" => "string"}, + "OptimizationUsed" => %{"type" => "string"}, + "DecompiledSourceCode" => %{"type" => "string"}, + "DecompilerVersion" => %{"type" => "string"} + } + } + }) + end + + # defp verify_schema do + # resolve_schema(%{ + # "type" => "object", + # "properties" => %{ + # "Address" => %{"type" => "string"}, + # "SourceCode" => %{"type" => "string"}, + # "ABI" => %{"type" => "string"}, + # "ContractName" => %{"type" => "string"}, + # "CompilerVersion" => %{"type" => "string"}, + # "DecompiledSourceCode" => %{"type" => "string"}, + # "DecompilerVersion" => %{"type" => "string"}, + # "OptimizationUsed" => %{"type" => "string"} + # } + # }) + # end + + defp resolve_schema(result) do + %{ + "type" => "object", + "properties" => %{ + "message" => %{"type" => "string"}, + "status" => %{"type" => "string"} + } + } + |> put_in(["properties", "result"], result) + |> ExJsonSchema.Schema.resolve() + end + + def get_implementation 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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs new file mode 100644 index 0000000..4dee425 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -0,0 +1,540 @@ +defmodule BlockScoutWeb.API.RPC.EthControllerTest do + use BlockScoutWeb.ConnCase, async: false + + alias Explorer.Counters.{AddressesCounter, AverageBlockTime} + alias Explorer.Repo + alias Indexer.Fetcher.CoinBalanceOnDemand + + setup do + mocked_json_rpc_named_arguments = [ + transport: EthereumJSONRPC.Mox, + transport_options: [] + ] + + start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) + start_supervised!(AverageBlockTime) + start_supervised!({CoinBalanceOnDemand, [mocked_json_rpc_named_arguments, [name: CoinBalanceOnDemand]]}) + start_supervised!(AddressesCounter) + + Application.put_env(:explorer, AverageBlockTime, enabled: true) + + on_exit(fn -> + Application.put_env(:explorer, AverageBlockTime, enabled: false) + end) + + :ok + end + + defp params(api_params, params), do: Map.put(api_params, "params", params) + + describe "eth_get_logs" do + setup do + %{ + api_params: %{ + "method" => "eth_getLogs", + "jsonrpc" => "2.0", + "id" => 0 + } + } + end + + test "with an invalid address", %{conn: conn, api_params: api_params} do + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [%{"address" => "badhash"}])) + |> json_response(200) + + assert %{"error" => "invalid address"} = response + end + + test "address with no logs", %{conn: conn, api_params: api_params} do + insert(:block) + address = insert(:address) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [%{"address" => to_string(address.hash)}])) + |> json_response(200) + + assert %{"result" => []} = response + end + + test "address but no logs and no toBlock provided", %{conn: conn, api_params: api_params} do + address = insert(:address) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [%{"address" => to_string(address.hash)}])) + |> json_response(200) + + assert %{"result" => []} = response + end + + test "with a matching address", %{conn: conn, api_params: api_params} do + address = insert(:address) + + block = insert(:block, number: 0) + + transaction = insert(:transaction, from_address: address) |> with_block(block) + insert(:log, block: block, address: address, transaction: transaction, data: "0x010101") + + params = params(api_params, [%{"address" => to_string(address.hash)}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert %{"result" => [%{"data" => "0x010101"}]} = response + end + + test "with a matching address and matching topic", %{conn: conn, api_params: api_params} do + address = insert(:address) + + block = insert(:block, number: 0) + + transaction = insert(:transaction, from_address: address) |> with_block(block) + insert(:log, block: block, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01") + + params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert %{"result" => [%{"data" => "0x010101"}]} = response + end + + test "with a matching address and multiple topic matches", %{conn: conn, api_params: api_params} do + address = insert(:address) + + block = insert(:block, number: 0) + + transaction = insert(:transaction, from_address: address) |> with_block(block) + insert(:log, address: address, block: block, transaction: transaction, data: "0x010101", first_topic: "0x01") + insert(:log, address: address, block: block, transaction: transaction, data: "0x020202", first_topic: "0x00") + + params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data")) + end + + test "paginates logs", %{conn: conn, api_params: api_params} do + contract_address = insert(:contract_address) + block = insert(:block) + + transaction = + :transaction + |> insert(to_address: contract_address) + |> with_block(block) + + inserted_records = + insert_list(2000, :log, block: block, address: contract_address, transaction: transaction, first_topic: "0x01") + + params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert Enum.count(response["result"]) == 1000 + + {last_log_index, ""} = Integer.parse(List.last(response["result"])["logIndex"], 16) + + next_page_params = %{ + "blockNumber" => Integer.to_string(transaction.block_number, 16), + "transactionIndex" => transaction.index, + "logIndex" => Integer.to_string(last_log_index, 16) + } + + new_params = + params(api_params, [ + %{"paging_options" => next_page_params, "address" => to_string(contract_address), "topics" => [["0x01"]]} + ]) + + assert new_response = + conn + |> post("/api/eth-rpc", new_params) + |> json_response(200) + + assert Enum.count(response["result"]) == 1000 + + all_found_logs = response["result"] ++ new_response["result"] + + assert Enum.all?(inserted_records, fn record -> + Enum.any?(all_found_logs, fn found_log -> + {index, ""} = Integer.parse(found_log["logIndex"], 16) + + record.index == index + end) + end) + end + + test "with a matching address and multiple topic matches in different positions", %{ + conn: conn, + api_params: api_params + } do + address = insert(:address) + + block = insert(:block, number: 0) + + transaction = insert(:transaction, from_address: address) |> with_block(block) + + insert(:log, + address: address, + transaction: transaction, + data: "0x010101", + first_topic: "0x01", + second_topic: "0x02", + block: block + ) + + insert(:log, block: block, address: address, transaction: transaction, data: "0x020202", first_topic: "0x01") + + params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", "0x02"]}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert [%{"data" => "0x010101"}] = response["result"] + end + + test "with a matching address and multiple topic matches in different positions and multiple matches in the second position", + %{conn: conn, api_params: api_params} do + address = insert(:address) + + block = insert(:block, number: 0) + + transaction = insert(:transaction, from_address: address) |> with_block(block) + + insert(:log, + address: address, + transaction: transaction, + data: "0x010101", + first_topic: "0x01", + second_topic: "0x02", + block: block + ) + + insert(:log, + address: address, + transaction: transaction, + data: "0x020202", + first_topic: "0x01", + second_topic: "0x03", + block: block + ) + + params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", ["0x02", "0x03"]]}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data")) + end + + test "with a block range filter", + %{conn: conn, api_params: api_params} do + address = insert(:address) + + block1 = insert(:block, number: 0) + block2 = insert(:block, number: 1) + block3 = insert(:block, number: 2) + block4 = insert(:block, number: 3) + + transaction1 = insert(:transaction, from_address: address) |> with_block(block1) + transaction2 = insert(:transaction, from_address: address) |> with_block(block2) + transaction3 = insert(:transaction, from_address: address) |> with_block(block3) + transaction4 = insert(:transaction, from_address: address) |> with_block(block4) + + insert(:log, address: address, transaction: transaction1, data: "0x010101") + + insert(:log, address: address, transaction: transaction2, data: "0x020202") + + insert(:log, address: address, transaction: transaction3, data: "0x030303") + + insert(:log, address: address, transaction: transaction4, data: "0x040404") + + params = params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => 1, "toBlock" => 2}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert [%{"data" => "0x020202"}, %{"data" => "0x030303"}] = Enum.sort_by(response["result"], &Map.get(&1, "data")) + end + + test "with a block hash filter", + %{conn: conn, api_params: api_params} do + address = insert(:address) + + block1 = insert(:block, number: 0) + block2 = insert(:block, number: 1) + block3 = insert(:block, number: 2) + + transaction1 = insert(:transaction, from_address: address) |> with_block(block1) + transaction2 = insert(:transaction, from_address: address) |> with_block(block2) + transaction3 = insert(:transaction, from_address: address) |> with_block(block3) + + insert(:log, address: address, transaction: transaction1, data: "0x010101") + + insert(:log, address: address, transaction: transaction2, data: "0x020202") + + insert(:log, address: address, transaction: transaction3, data: "0x030303") + + params = params(api_params, [%{"address" => to_string(address.hash), "blockHash" => to_string(block2.hash)}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert [%{"data" => "0x020202"}] = response["result"] + end + + test "with an earliest block filter", + %{conn: conn, api_params: api_params} do + address = insert(:address) + + block1 = insert(:block, number: 0) + block2 = insert(:block, number: 1) + block3 = insert(:block, number: 2) + + transaction1 = insert(:transaction, from_address: address) |> with_block(block1) + transaction2 = insert(:transaction, from_address: address) |> with_block(block2) + transaction3 = insert(:transaction, from_address: address) |> with_block(block3) + + insert(:log, address: address, transaction: transaction1, data: "0x010101") + + insert(:log, address: address, transaction: transaction2, data: "0x020202") + + insert(:log, address: address, transaction: transaction3, data: "0x030303") + + params = + params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => "earliest", "toBlock" => "earliest"}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert [%{"data" => "0x010101"}] = response["result"] + end + + test "with a pending block filter", + %{conn: conn, api_params: api_params} do + address = insert(:address) + + block1 = insert(:block, number: 0) + block2 = insert(:block, number: 1) + block3 = insert(:block, number: 2) + + transaction1 = insert(:transaction, from_address: address) |> with_block(block1) + transaction2 = insert(:transaction, from_address: address) |> with_block(block2) + transaction3 = insert(:transaction, from_address: address) |> with_block(block3) + + insert(:log, block: block1, address: address, transaction: transaction1, data: "0x010101") + + insert(:log, block: block2, address: address, transaction: transaction2, data: "0x020202") + + insert(:log, block: block3, address: address, transaction: transaction3, data: "0x030303") + + changeset = Ecto.Changeset.change(block3, %{consensus: false}) + + Repo.update!(changeset) + + params = + params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => "pending", "toBlock" => "pending"}]) + + assert response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + assert [%{"data" => "0x030303"}] = response["result"] + end + end + + describe "eth_get_balance" do + setup do + %{ + api_params: %{ + "method" => "eth_getBalance", + "jsonrpc" => "2.0", + "id" => 0 + } + } + end + + test "with an invalid address", %{conn: conn, api_params: api_params} do + assert response = + conn + |> post("/api/eth-rpc", params(api_params, ["badHash"])) + |> json_response(200) + + assert %{"error" => "Query parameter 'address' is invalid"} = response + end + + test "with a valid address that has no balance", %{conn: conn, api_params: api_params} do + address = insert(:address) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash)])) + |> json_response(200) + + assert %{"error" => "Balance not found"} = response + end + + test "with a valid address that has a balance", %{conn: conn, api_params: api_params} do + block = insert(:block) + address = insert(:address) + + insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash)])) + |> json_response(200) + + assert %{"result" => "0x1"} = response + end + + test "with a valid address that has no earliest balance", %{conn: conn, api_params: api_params} do + block = insert(:block, number: 1) + address = insert(:address) + + insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash), "earliest"])) + |> json_response(200) + + assert response["error"] == "Balance not found" + end + + test "with a valid address that has an earliest balance", %{conn: conn, api_params: api_params} do + block = insert(:block, number: 0) + address = insert(:address) + + insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash), "earliest"])) + |> json_response(200) + + assert response["result"] == "0x1" + end + + test "with a valid address and no pending balance", %{conn: conn, api_params: api_params} do + block = insert(:block, number: 1, consensus: true) + address = insert(:address) + + insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash), "pending"])) + |> json_response(200) + + assert response["error"] == "Balance not found" + end + + test "with a valid address and a pending balance", %{conn: conn, api_params: api_params} do + block = insert(:block, number: 1, consensus: false) + address = insert(:address) + + insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash), "pending"])) + |> json_response(200) + + assert response["result"] == "0x1" + end + + test "with a valid address and a pending balance after a consensus block", %{conn: conn, api_params: api_params} do + insert(:block, number: 1, consensus: true) + block = insert(:block, number: 2, consensus: false) + address = insert(:address) + + insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash), "pending"])) + |> json_response(200) + + assert response["result"] == "0x1" + end + + test "with a block provided", %{conn: conn, api_params: api_params} do + address = insert(:address) + + insert(:fetched_balance, block_number: 1, address_hash: address.hash, value: 1) + insert(:fetched_balance, block_number: 2, address_hash: address.hash, value: 2) + insert(:fetched_balance, block_number: 3, address_hash: address.hash, value: 3) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash), "2"])) + |> json_response(200) + + assert response["result"] == "0x2" + end + + test "with a block provided and no balance", %{conn: conn, api_params: api_params} do + address = insert(:address) + + insert(:fetched_balance, block_number: 3, address_hash: address.hash, value: 3) + + assert response = + conn + |> post("/api/eth-rpc", params(api_params, [to_string(address.hash), "2"])) + |> json_response(200) + + assert response["error"] == "Balance not found" + end + + test "with a batch of requests", %{conn: conn} do + address = insert(:address) + + insert(:fetched_balance, block_number: 1, address_hash: address.hash, value: 1) + insert(:fetched_balance, block_number: 2, address_hash: address.hash, value: 2) + insert(:fetched_balance, block_number: 3, address_hash: address.hash, value: 3) + + params = [ + %{"id" => 0, "params" => [to_string(address.hash), "1"], "jsonrpc" => "2.0", "method" => "eth_getBalance"}, + %{"id" => 1, "params" => [to_string(address.hash), "2"], "jsonrpc" => "2.0", "method" => "eth_getBalance"}, + %{"id" => 2, "params" => [to_string(address.hash), "3"], "jsonrpc" => "2.0", "method" => "eth_getBalance"} + ] + + assert response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/eth-rpc", Jason.encode!(params)) + |> json_response(200) + + assert [ + %{"id" => 0, "result" => "0x1"}, + %{"id" => 1, "result" => "0x2"}, + %{"id" => 2, "result" => "0x3"} + ] = response + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs new file mode 100644 index 0000000..ec4337e --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs @@ -0,0 +1,820 @@ +defmodule BlockScoutWeb.API.RPC.LogsControllerTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.API.RPC.LogsController + alias Explorer.Chain.{Log, Transaction} + + describe "getLogs" do + test "without fromBlock, toBlock, address, and topic{x}", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs" + } + + expected_message = "Required query parameters missing: fromBlock, toBlock, address and/or topic{x}" + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == expected_message + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "without fromBlock", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "toBlock" => "10", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + expected_message = "Required query parameters missing: fromBlock" + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == expected_message + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "without toBlock", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + expected_message = "Required query parameters missing: toBlock" + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == expected_message + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "without address and topic{x}", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "toBlock" => "10" + } + + expected_message = "Required query parameters missing: address and/or topic{x}" + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == expected_message + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "without topic{x}_{x}_opr", %{conn: conn} do + conditions = %{ + ["topic0", "topic1"] => "topic0_1_opr", + ["topic0", "topic2"] => "topic0_2_opr", + ["topic0", "topic3"] => "topic0_3_opr", + ["topic1", "topic2"] => "topic1_2_opr", + ["topic1", "topic3"] => "topic1_3_opr", + ["topic2", "topic3"] => "topic2_3_opr" + } + + for {[key1, key2], expectation} <- conditions do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "toBlock" => "10", + key1 => "some topic", + key2 => "some other topic" + } + + expected_message = "Required query parameters missing: #{expectation}" + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == expected_message + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + end + + test "without multiple topic{x}_{x}_opr", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "toBlock" => "10", + "topic0" => "some topic", + "topic1" => "some other topic", + "topic2" => "some extra topic", + "topic3" => "some different topic" + } + + expected_message = + "Required query parameters missing: " <> + "topic0_1_opr, topic0_2_opr, topic0_3_opr, topic1_2_opr, topic1_3_opr, topic2_3_opr" + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == expected_message + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with invalid fromBlock", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "invalid", + "toBlock" => "10", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid fromBlock format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with invalid toBlock", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "toBlock" => "invalid", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid toBlock format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with an invalid address hash", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "toBlock" => "10", + "address" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with invalid topic{x}_{x}_opr", %{conn: conn} do + conditions = %{ + ["topic0", "topic1"] => "topic0_1_opr", + ["topic0", "topic2"] => "topic0_2_opr", + ["topic0", "topic3"] => "topic0_3_opr", + ["topic1", "topic2"] => "topic1_2_opr", + ["topic1", "topic3"] => "topic1_3_opr", + ["topic2", "topic3"] => "topic2_3_opr" + } + + for {[key1, key2], expectation} <- conditions do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "toBlock" => "10", + key1 => "some topic", + key2 => "some other topic", + expectation => "invalid" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid #{expectation} format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + end + + test "with an address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "5", + "toBlock" => "10", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No logs found" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with a valid contract address", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert(to_address: contract_address) + |> with_block() + + log = insert(:log, address: contract_address, transaction: transaction) + + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "#{block.number}", + "toBlock" => "#{block.number}", + "address" => "#{contract_address.hash}" + } + + expected_result = [ + %{ + "address" => "#{contract_address.hash}", + "topics" => get_topics(log), + "data" => "#{log.data}", + "blockNumber" => integer_to_hex(transaction.block_number), + "timeStamp" => datetime_to_hex(block.timestamp), + "gasPrice" => decimal_to_hex(transaction.gas_price.value), + "gasUsed" => decimal_to_hex(transaction.gas_used), + "logIndex" => integer_to_hex(log.index), + "transactionHash" => "#{transaction.hash}", + "transactionIndex" => integer_to_hex(transaction.index) + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "ignores logs with block below fromBlock", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + + contract_address = insert(:contract_address) + + transaction_block1 = + %Transaction{} = + :transaction + |> insert(to_address: contract_address) + |> with_block(first_block) + + transaction_block2 = + %Transaction{} = + :transaction + |> insert(to_address: contract_address) + |> with_block(second_block) + + insert(:log, address: contract_address, transaction: transaction_block1) + insert(:log, address: contract_address, transaction: transaction_block2) + + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "#{second_block.number}", + "toBlock" => "#{second_block.number}", + "address" => "#{contract_address.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + + [found_log] = response["result"] + + assert found_log["blockNumber"] == integer_to_hex(second_block.number) + assert found_log["transactionHash"] == "#{transaction_block2.hash}" + end + + test "ignores logs with block above toBlock", %{conn: conn} do + first_block = insert(:block) + second_block = insert(:block) + + contract_address = insert(:contract_address) + + transaction_block1 = + %Transaction{} = + :transaction + |> insert(to_address: contract_address) + |> with_block(first_block) + + transaction_block2 = + %Transaction{} = + :transaction + |> insert(to_address: contract_address) + |> with_block(second_block) + + insert(:log, address: contract_address, transaction: transaction_block1) + insert(:log, address: contract_address, transaction: transaction_block2) + + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "#{first_block.number}", + "toBlock" => "#{first_block.number}", + "address" => "#{contract_address.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + + [found_log] = response["result"] + + assert found_log["blockNumber"] == integer_to_hex(first_block.number) + assert found_log["transactionHash"] == "#{transaction_block1.hash}" + end + + test "with a valid topic{x}", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert() + |> with_block() + + log1_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some topic" + ] + + log2_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some other topic" + ] + + log1 = insert(:log, log1_details) + _log2 = insert(:log, log2_details) + + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "#{block.number}", + "toBlock" => "#{block.number}", + "topic0" => log1.first_topic + } + + expected_result = [ + %{ + "address" => "#{contract_address.hash}", + "topics" => get_topics(log1), + "data" => "#{log1.data}", + "blockNumber" => integer_to_hex(transaction.block_number), + "timeStamp" => datetime_to_hex(block.timestamp), + "gasPrice" => decimal_to_hex(transaction.gas_price.value), + "gasUsed" => decimal_to_hex(transaction.gas_used), + "logIndex" => integer_to_hex(log1.index), + "transactionHash" => "#{transaction.hash}", + "transactionIndex" => integer_to_hex(transaction.index) + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with a topic{x} AND another", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert() + |> with_block() + + log1_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some topic", + second_topic: "some second topic" + ] + + log2_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some other topic", + second_topic: "some other second topic" + ] + + log1 = insert(:log, log1_details) + _log2 = insert(:log, log2_details) + + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "#{block.number}", + "toBlock" => "#{block.number}", + "topic0" => log1.first_topic, + "topic1" => log1.second_topic, + "topic0_1_opr" => "and" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert [found_log] = response["result"] + assert found_log["logIndex"] == integer_to_hex(log1.index) + assert found_log["topics"] == get_topics(log1) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with a topic{x} OR another", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert() + |> with_block() + + log1_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some topic", + second_topic: "some second topic" + ] + + log2_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some other topic", + second_topic: "some other second topic" + ] + + log1 = insert(:log, log1_details) + log2 = insert(:log, log2_details) + + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "#{block.number}", + "toBlock" => "#{block.number}", + "topic0" => log1.first_topic, + "topic1" => log2.second_topic, + "topic0_1_opr" => "or" + } + + assert %{"result" => result} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(result) == 2 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + + test "with all available 'topic{x}'s and 'topic{x}_{x}_opr's", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert() + |> with_block() + + log1_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some topic", + second_topic: "some second topic", + third_topic: "some third topic", + fourth_topic: "some fourth topic" + ] + + log2_details = [ + address: contract_address, + transaction: transaction, + first_topic: "some topic", + second_topic: "some second topic", + third_topic: "some third topic", + fourth_topic: "some other fourth topic" + ] + + log1 = insert(:log, log1_details) + log2 = insert(:log, log2_details) + + params = %{ + "module" => "logs", + "action" => "getLogs", + "fromBlock" => "#{block.number}", + "toBlock" => "#{block.number}", + "topic0" => log1.first_topic, + "topic1" => log1.second_topic, + "topic2" => log1.third_topic, + "topic3" => log2.fourth_topic, + "topic0_1_opr" => "and", + "topic0_2_opr" => "and", + "topic0_3_opr" => "or", + "topic1_2_opr" => "and", + "topic1_3_opr" => "or", + "topic2_3_opr" => "or" + } + + assert %{"result" => result} = + response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(result) == 2 + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getlogs_schema(), response) + end + end + + describe "fetch_required_params/1" do + test "without any required params" do + params = %{} + + {_, {:error, missing_params}} = LogsController.fetch_required_params(params) + + assert missing_params == ["fromBlock", "toBlock", "address and/or topic{x}"] + end + + test "without fromBlock" do + params = %{ + "toBlock" => "5", + "address" => "some address" + } + + {_, {:error, [missing_param]}} = LogsController.fetch_required_params(params) + + assert missing_param == "fromBlock" + end + + test "without toBlock" do + params = %{ + "fromBlock" => "5", + "address" => "some address" + } + + {_, {:error, [missing_param]}} = LogsController.fetch_required_params(params) + + assert missing_param == "toBlock" + end + + test "without fromBlock or toBlock" do + params = %{ + "address" => "some address" + } + + {_, {:error, missing_params}} = LogsController.fetch_required_params(params) + + assert missing_params == ["fromBlock", "toBlock"] + end + + test "without address or topic{x}" do + params = %{ + "toBlock" => "5", + "fromBlock" => "5" + } + + {_, {:error, [missing_param]}} = LogsController.fetch_required_params(params) + + assert missing_param == "address and/or topic{x}" + end + + test "with address" do + params = %{ + "fromBlock" => "5", + "toBlock" => "5", + "address" => "some address" + } + + {_, {:ok, fetched_params}} = LogsController.fetch_required_params(params) + + assert fetched_params == params + end + + test "with topic{x}" do + for topic <- ["topic0", "topic1", "topic2", "topic3"] do + params = %{ + "fromBlock" => "5", + "toBlock" => "5", + topic => "some topic" + } + + {_, {:ok, fetched_params}} = LogsController.fetch_required_params(params) + + assert fetched_params == params + end + end + + test "with address and topic{x}" do + params = %{ + "fromBlock" => "5", + "toBlock" => "5", + "address" => "some address", + "topic0" => "some topic" + } + + {_, {:ok, fetched_params}} = LogsController.fetch_required_params(params) + + assert fetched_params == params + end + end + + describe "to_valid_format/1" do + test "with invalid fromBlock" do + params = %{"fromBlock" => "invalid"} + + assert {_, {:error, "fromBlock"}} = LogsController.to_valid_format(params) + end + + test "with invalid toBlock" do + params = %{ + "fromBlock" => "5", + "toBlock" => "invalid" + } + + assert {_, {:error, "toBlock"}} = LogsController.to_valid_format(params) + end + + test "with invalid address" do + params = %{ + "fromBlock" => "5", + "toBlock" => "10", + "address" => "invalid" + } + + assert {_, {:error, "address"}} = LogsController.to_valid_format(params) + end + + test "address_hash returns as nil when missing" do + params = %{ + "fromBlock" => "5", + "toBlock" => "10" + } + + assert {_, {:ok, validated_params}} = LogsController.to_valid_format(params) + refute validated_params.address_hash + end + + test "fromBlock and toBlock support use of 'latest'" do + params = %{ + "fromBlock" => "latest", + "toBlock" => "latest" + } + + # Without any blocks in the db we want to return {:error, :not_found} + assert {_, {:error, :not_found}} = LogsController.to_valid_format(params) + + # We insert a block, try again, and assert 'latest' points to the latest + # block number. + insert(:block) + {:ok, max_consensus_block_number} = Explorer.Chain.max_consensus_block_number() + + assert {_, {:ok, validated_params}} = LogsController.to_valid_format(params) + assert validated_params.from_block == max_consensus_block_number + assert validated_params.to_block == max_consensus_block_number + end + end + + defp get_topics(%Log{ + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, + fourth_topic: fourth_topic + }) do + [first_topic, second_topic, third_topic, fourth_topic] + end + + defp integer_to_hex(integer), do: "0x" <> String.downcase(Integer.to_string(integer, 16)) + + defp decimal_to_hex(decimal) do + decimal + |> Decimal.to_integer() + |> integer_to_hex() + end + + defp datetime_to_hex(datetime) do + datetime + |> DateTime.to_unix() + |> integer_to_hex() + end + + defp getlogs_schema do + ExJsonSchema.Schema.resolve(%{ + "type" => "object", + "properties" => %{ + "message" => %{"type" => "string"}, + "status" => %{"type" => "string"}, + "result" => %{ + "type" => ["array", "null"], + "items" => %{ + "type" => "object", + "properties" => %{ + "address" => %{"type" => "string"}, + "topics" => %{"type" => "array", "items" => %{"type" => ["string", "null"]}}, + "data" => %{"type" => "string"}, + "blockNumber" => %{"type" => "string"}, + "timeStamp" => %{"type" => "string"}, + "gasPrice" => %{"type" => "string"}, + "gasUsed" => %{"type" => "string"}, + "logIndex" => %{"type" => "string"}, + "transactionHash" => %{"type" => "string"}, + "transactionIndex" => %{"type" => "string"} + } + } + } + } + }) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs new file mode 100644 index 0000000..a5b23b2 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs @@ -0,0 +1,82 @@ +defmodule BlockScoutWeb.API.RPC.RPCTranslatorTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.API.RPC.RPCTranslator + alias Plug.Conn + + defmodule TestController do + use BlockScoutWeb, :controller + + def test_action(conn, _) do + json(conn, %{}) + end + end + + setup %{conn: conn} do + conn = Phoenix.Controller.accepts(conn, ["json"]) + {:ok, conn: conn} + end + + test "init/1" do + assert RPCTranslator.init([]) == [] + end + + describe "call" do + test "with a bad module", %{conn: conn} do + conn = %Conn{conn | params: %{"module" => "test", "action" => "test"}, request_path: "/api"} + + result = RPCTranslator.call(conn, %{}) + assert result.halted + assert response = json_response(result, 400) + assert response["message"] =~ "Unknown action" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a bad action atom", %{conn: conn} do + conn = %Conn{ + conn + | params: %{"module" => "test", "action" => "some_atom_that_should_not_exist"}, + request_path: "/api" + } + + result = RPCTranslator.call(conn, %{"test" => {TestController, []}}) + assert result.halted + assert response = json_response(result, 400) + assert response["message"] =~ "Unknown action" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an invalid controller action", %{conn: conn} do + conn = %Conn{conn | params: %{"module" => "test", "action" => "index"}, request_path: "/api"} + + result = RPCTranslator.call(conn, %{"test" => {TestController, []}}) + assert result.halted + assert response = json_response(result, 400) + assert response["message"] =~ "Unknown action" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with missing params", %{conn: conn} do + result = RPCTranslator.call(conn, %{"test" => {TestController, []}}) + assert result.halted + assert response = json_response(result, 400) + assert response["message"] =~ "'module' and 'action' are required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a valid request", %{conn: conn} do + conn = %Conn{conn | params: %{"module" => "test", "action" => "test_action"}, request_path: "/api"} + + result = RPCTranslator.call(conn, %{"test" => {TestController, []}}) + assert json_response(result, 200) == %{} + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs new file mode 100644 index 0000000..e92ba9b --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs @@ -0,0 +1,247 @@ +defmodule BlockScoutWeb.API.RPC.StatsControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + alias Explorer.ExchangeRates + alias Explorer.ExchangeRates.Token + alias Explorer.ExchangeRates.Source.TestSource + + describe "tokensupply" do + test "with missing contract address", %{conn: conn} do + params = %{ + "module" => "stats", + "action" => "tokensupply" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "contract address is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokensupply_schema(), response) + end + + test "with an invalid contract address hash", %{conn: conn} do + params = %{ + "module" => "stats", + "action" => "tokensupply", + "contractaddress" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid contract address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokensupply_schema(), response) + end + + test "with a contract address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "stats", + "action" => "tokensupply", + "contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "contract address not found" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokensupply_schema(), response) + end + + test "with valid contract address", %{conn: conn} do + token = insert(:token) + + params = %{ + "module" => "stats", + "action" => "tokensupply", + "contractaddress" => to_string(token.contract_address_hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == to_string(token.total_supply) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokensupply_schema(), response) + end + end + + describe "ethsupplyexchange" do + test "returns total supply from exchange", %{conn: conn} do + params = %{ + "module" => "stats", + "action" => "ethsupplyexchange" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == "252460800000000000000000000" + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(ethsupplyexchange_schema(), response) + end + end + + # todo: Temporarily disable this test because of unstable work in CI + # describe "ethsupply" do + # test "returns total supply from DB", %{conn: conn} do + # params = %{ + # "module" => "stats", + # "action" => "ethsupply" + # } + + # assert response = + # conn + # |> get("/api", params) + # |> json_response(200) + + # assert response["result"] == "0" + # assert response["status"] == "1" + # assert response["message"] == "OK" + # assert :ok = ExJsonSchema.Validator.validate(ethsupply_schema(), response) + # end + # end + + # describe "coinsupply" do + # test "returns total supply minus a burnt number from DB in coins denomination", %{conn: conn} do + # params = %{ + # "module" => "stats", + # "action" => "coinsupply" + # } + + # assert response = + # conn + # |> get("/api", params) + # |> json_response(200) + + # assert response == 0.0 + # end + # end + + describe "coinprice" do + setup :set_mox_global + + setup do + # Use TestSource mock for this test set + configuration = Application.get_env(:explorer, Explorer.ExchangeRates) + Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) + Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) + + ExchangeRates.init([]) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, Explorer.ExchangeRates, configuration) + end) + end + + test "returns the configured coin's price information", %{conn: conn} do + symbol = Application.get_env(:explorer, :coin) + + eth = %Token{ + available_supply: Decimal.new("1000000.0"), + total_supply: Decimal.new("1000000.0"), + btc_value: Decimal.new("1.000"), + id: "test", + last_updated: DateTime.utc_now(), + market_cap_usd: Decimal.new("1000000.0"), + name: "test", + symbol: symbol, + usd_value: Decimal.new("1.0"), + volume_24h_usd: Decimal.new("1000.0") + } + + ExchangeRates.handle_info({nil, {:ok, [eth]}}, %{}) + + params = %{ + "module" => "stats", + "action" => "coinprice" + } + + expected_timestamp = eth.last_updated |> DateTime.to_unix() |> to_string() + + expected_result = %{ + "coin_btc" => to_string(eth.btc_value), + "coin_btc_timestamp" => expected_timestamp, + "coin_usd" => to_string(eth.usd_value), + "coin_usd_timestamp" => expected_timestamp + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(coinprice_schema(), response) + end + end + + defp tokensupply_schema do + resolve_schema(%{ + "type" => ["string", "null"] + }) + end + + # defp ethsupply_schema do + # resolve_schema(%{ + # "type" => ["string", "null"] + # }) + # end + + defp ethsupplyexchange_schema do + resolve_schema(%{ + "type" => ["string", "null"] + }) + end + + defp coinprice_schema do + resolve_schema(%{ + "type" => "object", + "properties" => %{ + "coin_btc" => %{"type" => "string"}, + "coin_btc_timestamp" => %{"type" => "string"}, + "coin_usd" => %{"type" => "string"}, + "coin_usd_timestamp" => %{"type" => "string"} + } + }) + end + + defp resolve_schema(result) do + %{ + "type" => "object", + "properties" => %{ + "message" => %{"type" => "string"}, + "status" => %{"type" => "string"} + } + } + |> put_in(["properties", "result"], result) + |> ExJsonSchema.Schema.resolve() + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/token_controller_test.exs new file mode 100644 index 0000000..f33dff9 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/token_controller_test.exs @@ -0,0 +1,109 @@ +defmodule BlockScoutWeb.API.RPC.TokenControllerTest do + use BlockScoutWeb.ConnCase + + describe "gettoken" do + test "with missing contract address", %{conn: conn} do + params = %{ + "module" => "token", + "action" => "getToken" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "contract address is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an invalid contract address hash", %{conn: conn} do + params = %{ + "module" => "token", + "action" => "getToken", + "contractaddress" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid contract address hash" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a contract address that doesn't exist", %{conn: conn} do + params = %{ + "module" => "token", + "action" => "getToken", + "contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "contract address not found" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "response includes all required fields", %{conn: conn} do + token = insert(:token) + + params = %{ + "module" => "token", + "action" => "getToken", + "contractaddress" => to_string(token.contract_address_hash) + } + + expected_result = %{ + "name" => token.name, + "symbol" => token.symbol, + "totalSupply" => to_string(token.total_supply), + "decimals" => to_string(token.decimals), + "type" => token.type, + "cataloged" => token.cataloged, + "contractAddress" => to_string(token.contract_address_hash) + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + end + + # defp gettoken_schema do + # ExJsonSchema.Schema.resolve(%{ + # "type" => "object", + # "properties" => %{ + # "message" => %{"type" => "string"}, + # "status" => %{"type" => "string"}, + # "result" => %{ + # "type" => "object", + # "properties" => %{ + # "name" => %{"type" => "string"}, + # "symbol" => %{"type" => "string"}, + # "totalSupply" => %{"type" => "string"}, + # "decimals" => %{"type" => "string"}, + # "type" => %{"type" => "string"}, + # "cataloged" => %{"type" => "string"}, + # "contractAddress" => %{"type" => "string"} + # } + # } + # } + # }) + # end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs new file mode 100644 index 0000000..d46071d --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -0,0 +1,773 @@ +defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + @moduletag capture_log: true + + describe "gettxreceiptstatus" do + test "with missing txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxreceiptstatus" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = resolve_schema() + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["message"] =~ "txhash is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an invalid txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxreceiptstatus", + "txhash" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = resolve_schema() + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["message"] =~ "Invalid txhash format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a txhash that doesn't exist", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxreceiptstatus", + "txhash" => "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" + } + + schema = + resolve_schema(%{ + "type" => "object", + "properties" => %{ + "status" => %{"type" => "string"} + } + }) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["result"] == %{"status" => ""} + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with ok status", %{conn: conn} do + block = insert(:block) + + transaction = + :transaction + |> insert() + |> with_block(block, status: :ok) + + params = %{ + "module" => "transaction", + "action" => "gettxreceiptstatus", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == %{"status" => "1"} + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with error status", %{conn: conn} do + block = insert(:block) + + transaction = + :transaction + |> insert() + |> with_block(block, status: :error) + + params = %{ + "module" => "transaction", + "action" => "gettxreceiptstatus", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == %{"status" => "0"} + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with nil status", %{conn: conn} do + transaction = insert(:transaction, status: nil) + + params = %{ + "module" => "transaction", + "action" => "gettxreceiptstatus", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == %{"status" => ""} + assert response["status"] == "1" + assert response["message"] == "OK" + end + end + + describe "getstatus" do + test "with missing txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "getstatus" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = resolve_schema() + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["message"] =~ "txhash is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an invalid txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "getstatus", + "txhash" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = resolve_schema() + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["message"] =~ "Invalid txhash format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a txhash that doesn't exist", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "getstatus", + "txhash" => "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" + } + + expected_result = %{ + "isError" => "0", + "errDescription" => "" + } + + schema = + resolve_schema(%{ + "type" => "object", + "properties" => %{ + "isError" => %{"type" => "string"}, + "errDescription" => %{"type" => "string"} + } + }) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with ok status", %{conn: conn} do + block = insert(:block) + + transaction = + :transaction + |> insert() + |> with_block(block, status: :ok) + + params = %{ + "module" => "transaction", + "action" => "getstatus", + "txhash" => "#{transaction.hash}" + } + + expected_result = %{ + "isError" => "0", + "errDescription" => "" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with error", %{conn: conn} do + error = "some error" + + transaction_details = [ + status: :error, + error: error + ] + + transaction = + :transaction + |> insert() + |> with_block(transaction_details) + + internal_transaction_details = [ + transaction: transaction, + index: 0, + type: :reward, + error: error, + block_hash: transaction.block_hash, + block_index: 0 + ] + + insert(:internal_transaction, internal_transaction_details) + + params = %{ + "module" => "transaction", + "action" => "getstatus", + "txhash" => "#{transaction.hash}" + } + + expected_result = %{ + "isError" => "1", + "errDescription" => error + } + + response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with failed status but awaiting internal transactions", %{conn: conn} do + transaction_details = [ + status: :error, + error: nil + ] + + transaction = + :transaction + |> insert() + |> with_block(transaction_details) + + params = %{ + "module" => "transaction", + "action" => "getstatus", + "txhash" => "#{transaction.hash}" + } + + expected_result = %{ + "isError" => "1", + "errDescription" => "awaiting internal transactions" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with nil status", %{conn: conn} do + transaction = insert(:transaction, status: nil) + + params = %{ + "module" => "transaction", + "action" => "getstatus", + "txhash" => "#{transaction.hash}" + } + + expected_result = %{ + "isError" => "0", + "errDescription" => "" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + end + + describe "gettxinfo" do + test "with missing txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxinfo" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + schema = resolve_schema() + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["message"] =~ "txhash is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an invalid txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid txhash format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a txhash that doesn't exist", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Transaction not found" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "paginates logs", %{conn: conn} do + block = insert(:block, hash: "0x30d522bcf2d8e0cabc286e6e40623c475c3bc05d0ec484ea239c103b1ac0ded9", number: 99) + + transaction = + :transaction + |> insert(hash: "0x13b6bb8e06322096dc83e8d7e6332ca19919ea642212cd259c6b20e7523a0599") + |> with_block(block, status: :ok) + + address = insert(:address) + + Enum.each(1..100, fn _ -> + insert(:log, + address: address, + transaction: transaction, + first_topic: "first topic", + second_topic: "second topic", + block: block, + block_number: block.number + ) + end) + + params1 = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + schema = + resolve_schema(%{ + "type" => "object", + "properties" => %{ + "next_page_params" => %{ + "type" => ["object", "null"], + "properties" => %{ + "action" => %{"type" => "string"}, + "index" => %{"type" => "number"}, + "module" => %{"type" => "string"}, + "txhash" => %{"type" => "string"} + } + }, + "logs" => %{ + "type" => "array", + "items" => %{"type" => "object"} + } + } + }) + + assert response1 = + conn + |> get("/api", params1) + |> json_response(200) + + assert ExJsonSchema.Validator.valid?(schema, response1) + assert response1["status"] == "1" + assert response1["message"] == "OK" + + assert %{ + "action" => "gettxinfo", + "index" => _, + "module" => "transaction", + "txhash" => _ + } = response1["result"]["next_page_params"] + + params2 = response1["result"]["next_page_params"] + + assert response2 = + conn + |> get("/api", params2) + |> json_response(200) + + assert ExJsonSchema.Validator.valid?(schema, response2) + assert response2["status"] == "1" + assert response2["message"] == "OK" + assert is_nil(response2["result"]["next_page_params"]) + assert response1["result"]["logs"] != response2["result"]["logs"] + end + + test "with a txhash with ok status", %{conn: conn} do + block = insert(:block) + + transaction = + :transaction + |> insert() + |> with_block(block, status: :ok) + + address = insert(:address) + + log = + insert(:log, + address: address, + transaction: transaction, + first_topic: "first topic", + second_topic: "second topic", + block: block, + block_number: block.number + ) + + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + expected_result = %{ + "hash" => "#{transaction.hash}", + "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}", + "blockNumber" => "#{transaction.block_number}", + "confirmations" => "0", + "success" => true, + "from" => "#{transaction.from_address_hash}", + "to" => "#{transaction.to_address_hash}", + "value" => "#{transaction.value.value}", + "input" => "#{transaction.input}", + "gasLimit" => "#{transaction.gas}", + "gasUsed" => "#{transaction.gas_used}", + "gasPrice" => "#{transaction.gas_price.value}", + "logs" => [ + %{ + "address" => "#{address.hash}", + "data" => "#{log.data}", + "topics" => ["first topic", "second topic", nil, nil], + "index" => "#{log.index}" + } + ], + "next_page_params" => nil, + "revertReason" => "" + } + + schema = + resolve_schema(%{ + "type" => "object", + "properties" => %{ + "hash" => %{"type" => "string"}, + "timeStamp" => %{"type" => "string"}, + "blockNumber" => %{"type" => "string"}, + "confirmations" => %{"type" => "string"}, + "success" => %{"type" => "boolean"}, + "from" => %{"type" => "string"}, + "to" => %{"type" => "string"}, + "value" => %{"type" => "string"}, + "input" => %{"type" => "string"}, + "gasLimit" => %{"type" => "string"}, + "gasUsed" => %{"type" => "string"}, + "gasPrice" => %{"type" => "string"}, + "logs" => %{ + "type" => "array", + "items" => %{ + "type" => "object", + "properties" => %{ + "address" => %{"type" => "string"}, + "data" => %{"type" => "string"}, + "topics" => %{ + "type" => "array", + "items" => %{"type" => ["string", "null"]} + }, + "index" => %{"type" => "string"} + } + } + }, + "next_page_params" => %{ + "type" => ["object", "null"], + "properties" => %{ + "action" => %{"type" => "string"}, + "index" => %{"type" => "number"}, + "module" => %{"type" => "string"}, + "txhash" => %{"type" => "string"} + } + } + } + }) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert ExJsonSchema.Validator.valid?(schema, response) + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with revert reason from DB", %{conn: conn} do + block = insert(:block, number: 100) + + transaction = + :transaction + |> insert(revert_reason: "No credit of that type") + |> with_block(block) + + insert(:address) + + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"]["revertReason"] == "No credit of that type" + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with empty revert reason from DB", %{conn: conn} do + block = insert(:block, number: 100) + + transaction = + :transaction + |> insert(revert_reason: "") + |> with_block(block) + + insert(:address) + + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"]["revertReason"] == "" + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with revert reason from the archive node", %{conn: conn} do + block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + + transaction = + :transaction + |> insert( + error: "Reverted", + status: :error, + block_hash: block.hash, + block_number: block.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + insert(:address) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn _json, [] -> + {:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}} + end + ) + + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"]["revertReason"] == "No credit of that type" + assert response["status"] == "1" + assert response["message"] == "OK" + end + end + + test "with a txhash with empty revert reason from the archive node", %{conn: conn} do + block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + + transaction = + :transaction + |> insert( + error: "Reverted", + status: :error, + block_hash: block.hash, + block_number: block.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + insert(:address) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn _json, [] -> + {:error, %{code: -32015, message: "VM execution error.", data: ""}} + end + ) + + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"]["revertReason"] == "" + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with a txhash with empty revert reason from DB if eth_call doesn't return an error", %{conn: conn} do + block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + + transaction = + :transaction + |> insert( + error: "Reverted", + status: :error, + block_hash: block.hash, + block_number: block.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + insert(:address) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn _json, [] -> + {:ok} + end + ) + + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"]["revertReason"] == "" + assert response["status"] == "1" + assert response["message"] == "OK" + end + + defp resolve_schema(result \\ %{}) do + %{ + "type" => "object", + "properties" => %{ + "message" => %{"type" => "string"}, + "status" => %{"type" => "string"} + } + } + |> put_in(["properties", "result"], result) + |> ExJsonSchema.Schema.resolve() + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs new file mode 100644 index 0000000..8f511da --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs @@ -0,0 +1,121 @@ +defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Repo + alias Explorer.Chain.{Address, DecompiledSmartContract} + + import Ecto.Query, + only: [from: 2] + + @secret "secret" + + describe "when used authorized" do + setup %{conn: conn} = context do + Application.put_env(:block_scout_web, :decompiled_smart_contract_token, @secret) + + auth_conn = conn |> put_req_header("auth_token", @secret) + + {:ok, Map.put(context, :conn, auth_conn)} + end + + test "returns unprocessable_entity status when params are invalid", %{conn: conn} do + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create)) + + assert request.status == 422 + assert request.resp_body == "{\"error\":\"address_hash is invalid\"}" + end + + test "returns unprocessable_entity when code is empty", %{conn: conn} do + decompiler_version = "test_decompiler" + address = insert(:address) + + params = %{ + "address_hash" => to_string(address.hash), + "decompiler_version" => decompiler_version + } + + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) + + assert request.status == 422 + assert request.resp_body == "{\"decompiled_source_code\":\"can't be blank\"}" + end + + test "can not update code for the same decompiler version", %{conn: conn} do + address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash) + decompiler_version = "test_decompiler" + decompiled_source_code = "hello world" + + insert(:decompiled_smart_contract, + address_hash: address_hash, + decompiler_version: decompiler_version, + decompiled_source_code: decompiled_source_code + ) + + params = %{ + "address_hash" => address_hash, + "decompiler_version" => decompiler_version, + "decompiled_source_code" => decompiled_source_code + } + + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) + + assert request.status == 422 + + assert request.resp_body == "{\"error\":\"decompiled code already exists for the decompiler version\"}" + end + + test "creates decompiled smart contract", %{conn: conn} do + address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash) + decompiler_version = "test_decompiler" + decompiled_source_code = "hello world" + + params = %{ + "address_hash" => address_hash, + "decompiler_version" => decompiler_version, + "decompiled_source_code" => decompiled_source_code + } + + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) + + assert request.status == 201 + + assert request.resp_body == + "{\"address_hash\":\"0x0000000000000000000000000000000000000001\",\"decompiler_version\":\"test_decompiler\",\"decompiled_source_code\":\"hello world\"}" + + decompiled_smart_contract = Repo.one!(from(d in DecompiledSmartContract, where: d.address_hash == ^address_hash)) + assert to_string(decompiled_smart_contract.address_hash) == address_hash + assert decompiled_smart_contract.decompiler_version == decompiler_version + assert decompiled_smart_contract.decompiled_source_code == decompiled_source_code + end + + test "updates the address to be decompiled", %{conn: conn} do + address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash) + decompiler_version = "test_decompiler" + decompiled_source_code = "hello world" + + params = %{ + "address_hash" => address_hash, + "decompiler_version" => decompiler_version, + "decompiled_source_code" => decompiled_source_code + } + + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) + + assert request.status == 201 + + assert Repo.get!(Address, address_hash).decompiled + end + end + + describe "when user is not authorized" do + test "returns forbedden", %{conn: conn} do + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create)) + + assert request.status == 403 + end + end + + defp api_v1_decompiled_smart_contract_path(conn, action) do + "/api" <> ApiRoutes.api_v1_decompiled_smart_contract_path(conn, action) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs new file mode 100644 index 0000000..54197c5 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs @@ -0,0 +1,94 @@ +defmodule BlockScoutWeb.API.V1.HealthControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.{Chain, PagingOptions} + + setup do + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + + :ok + end + + describe "GET last_block_status/0" do + test "returns error when there are no blocks in db", %{conn: conn} do + request = get(conn, api_v1_health_path(conn, :health)) + + assert request.status == 500 + + assert request.resp_body == + "{\"error_code\":5002,\"error_description\":\"There are no blocks in the DB\",\"error_title\":\"no blocks in db\",\"healthy\":false}" + end + + test "returns error when last block is stale", %{conn: conn} do + insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) + + request = get(conn, api_v1_health_path(conn, :health)) + + assert request.status == 500 + + assert %{ + "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", + "data" => %{ + "latest_block_number" => _, + "latest_block_inserted_at" => _ + } + } = Poison.decode!(request.resp_body) + end + + test "returns ok when last block is not stale", %{conn: conn} do + block1 = insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 2) + insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 1) + + request = get(conn, api_v1_health_path(conn, :health)) + + assert request.status == 200 + + result = Poison.decode!(request.resp_body) + + assert result["healthy"] == true + + assert %{ + "latest_block_number" => to_string(block1.number), + "latest_block_inserted_at" => to_string(block1.timestamp), + "cache_latest_block_number" => to_string(block1.number), + "cache_latest_block_inserted_at" => to_string(block1.timestamp) + } == result["data"] + end + end + + test "return error when cache is stale", %{conn: conn} do + stale_block = insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50), number: 3) + state_block_hash = stale_block.hash + + assert [%{hash: ^state_block_hash}] = Chain.list_blocks(paging_options: %PagingOptions{page_size: 1}) + + insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 1) + + assert [%{hash: ^state_block_hash}] = Chain.list_blocks(paging_options: %PagingOptions{page_size: 1}) + + request = get(conn, api_v1_health_path(conn, :health)) + + assert request.status == 500 + + assert %{ + "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", + "data" => %{ + "latest_block_number" => _, + "latest_block_inserted_at" => _ + } + } = Poison.decode!(request.resp_body) + end + + defp api_v1_health_path(conn, action) do + "/api" <> ApiRoutes.api_v1_health_path(conn, action) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/supply_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/supply_controller_test.exs new file mode 100644 index 0000000..b521838 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/supply_controller_test.exs @@ -0,0 +1,17 @@ +defmodule BlockScoutWeb.API.V1.SupplyControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain + + test "supply", %{conn: conn} do + request = get(conn, api_v1_supply_path(conn, :supply)) + assert response = json_response(request, 200) + + assert response["total_supply"] == Chain.total_supply() + assert response["circulating_supply"] == Chain.circulating_supply() + end + + def api_v1_supply_path(conn, action) do + "/api" <> ApiRoutes.api_v1_supply_path(conn, action) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/verified_smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/verified_smart_contract_controller_test.exs new file mode 100644 index 0000000..5726a85 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/verified_smart_contract_controller_test.exs @@ -0,0 +1,82 @@ +defmodule BlockScoutWeb.API.V1.VerifiedControllerTest do + use BlockScoutWeb.ConnCase + + # alias Explorer.Factory + + # alias Explorer.Chain.DecompiledSmartContract + + # import Ecto.Query, + # only: [from: 2] + + # flaky test + # test "verifying a standard smart contract", %{conn: conn} do + # contract_code_info = Factory.contract_code_info() + + # contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode) + # insert(:transaction, created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input) + + # params = %{ + # "address_hash" => to_string(contract_address.hash), + # "name" => contract_code_info.name, + # "compiler_version" => contract_code_info.version, + # "optimization" => contract_code_info.optimized, + # "contract_source_code" => contract_code_info.source_code + # } + + # response = post(conn, api_v1_verified_smart_contract_path(conn, :create), params) + + # assert response.status == 201 + # assert Jason.decode!(response.resp_body) == %{"status" => "success"} + # end + + # flaky test + # test "verifying a smart contract with external libraries", %{conn: conn} do + # contract_data = + # "#{File.cwd!()}/test/support/fixture/smart_contract/contract_with_lib.json" + # |> File.read!() + # |> Jason.decode!() + # |> List.first() + + # %{ + # "compiler_version" => compiler_version, + # "external_libraries" => external_libraries, + # "name" => name, + # "optimize" => optimize, + # "contract" => contract_source_code, + # "tx_input" => tx_input, + # "expected_bytecode" => expected_bytecode + # } = contract_data + + # contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode) + # insert(:transaction, created_contract_address_hash: contract_address.hash, input: "0x" <> tx_input) + + # params = %{ + # "address_hash" => to_string(contract_address.hash), + # "name" => name, + # "compiler_version" => compiler_version, + # "optimization" => optimize, + # "contract_source_code" => contract_source_code + # } + + # params_with_external_libraries = + # external_libraries + # |> Enum.with_index() + # |> Enum.reduce(params, fn {{name, address}, index}, acc -> + # name_key = "library#{index + 1}_name" + # address_key = "library#{index + 1}_address" + + # acc + # |> Map.put(name_key, name) + # |> Map.put(address_key, address) + # end) + + # response = post(conn, api_v1_verified_smart_contract_path(conn, :create), params_with_external_libraries) + + # assert response.status == 201 + # assert Jason.decode!(response.resp_body) == %{"status" => "success"} + # end + + # defp api_v1_verified_smart_contract_path(conn, action) do + # "/api" <> ApiRoutes.api_v1_verified_smart_contract_path(conn, action) + # end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api_docs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api_docs_controller_test.exs new file mode 100644 index 0000000..3b2cb99 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api_docs_controller_test.exs @@ -0,0 +1,18 @@ +defmodule BlockScoutWeb.APIDocsControllerTest do + use BlockScoutWeb.ConnCase + + import BlockScoutWeb.Router.Helpers, only: [api_docs_path: 2] + + describe "GET index/2" do + test "renders documentation tiles for each API module#action", %{conn: conn} do + conn = get(conn, api_docs_path(BlockScoutWeb.Endpoint, :index)) + + documentation = BlockScoutWeb.Etherscan.get_documentation() + + for module <- documentation, action <- module.actions do + assert html_response(conn, 200) =~ action.name + assert html_response(conn, 200) =~ action.description + end + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs new file mode 100644 index 0000000..b407e0a --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs @@ -0,0 +1,193 @@ +defmodule BlockScoutWeb.BlockControllerTest do + use BlockScoutWeb.ConnCase + alias Explorer.Chain.Block + + 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 "GET show/2" do + test "with block redirects to block transactions route", %{conn: conn} do + insert(:block, number: 3) + conn = get(conn, "/blocks/3") + assert redirected_to(conn) =~ "/block/3/transactions" + end + + test "with uncle block redirects to block_hash route", %{conn: conn} do + uncle = insert(:block, consensus: false) + + conn = get(conn, block_path(conn, :show, uncle)) + assert redirected_to(conn) =~ "/block/#{to_string(uncle.hash)}/transactions" + end + end + + describe "GET index/2" do + test "returns all blocks", %{conn: conn} do + 4 + |> insert_list(:block) + |> Stream.map(& &1.number) + |> Enum.reverse() + + conn = get(conn, blocks_path(conn, :index), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 4 + end + + test "does not include uncles", %{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 + + conn = get(conn, blocks_path(conn, :index), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 4 + end + + test "returns a block with two transactions", %{conn: conn} do + block = insert(:block) + + 2 + |> insert_list(:transaction) + |> with_block(block) + + conn = get(conn, blocks_path(conn, :index), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 1 + end + + test "returns next page of results based on last seen block", %{conn: conn} do + 50 + |> insert_list(:block) + |> Enum.map(& &1.number) + + block = insert(:block) + + conn = + get(conn, blocks_path(conn, :index), %{ + "type" => "JSON", + "block_number" => Integer.to_string(block.number) + }) + + 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 + %Block{number: number} = + 60 + |> insert_list(:block) + |> Enum.fetch!(10) + + conn = get(conn, blocks_path(conn, :index), %{"type" => "JSON"}) + + expected_path = + blocks_path(conn, :index, %{ + block_number: number, + block_type: "Block", + 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(:block) + + conn = get(conn, blocks_path(conn, :index), %{"type" => "JSON"}) + + refute conn |> json_response(200) |> Map.get("next_page_path") + end + + test "displays miner primary address name", %{conn: conn} do + miner_name = "POA Miner Pool" + %{address: miner_address} = insert(:address_name, name: miner_name, primary: true) + + insert(:block, miner: miner_address, miner_hash: nil) + + conn = get(conn, blocks_path(conn, :index), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert List.first(items) =~ miner_name + end + end + + describe "GET reorgs/2" do + test "returns all reorgs", %{conn: conn} do + 4 + |> insert_list(:block, consensus: false) + |> Enum.map(& &1.hash) + + conn = get(conn, reorg_path(conn, :reorg), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 4 + end + + test "does not include blocks or uncles", %{conn: conn} do + 4 + |> insert_list(:block, consensus: false) + |> Enum.map(& &1.hash) + + insert(:block) + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash) + + conn = get(conn, reorg_path(conn, :reorg), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 4 + end + end + + describe "GET uncle/2" do + test "returns all uncles", %{conn: conn} do + for _index <- 1..4 do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash) + end + + conn = get(conn, uncle_path(conn, :uncle), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 4 + end + + test "does not include blocks or reorgs", %{conn: conn} do + for _index <- 1..4 do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash) + end + + insert(:block) + insert(:block, consensus: false) + + conn = get(conn, uncle_path(conn, :uncle), %{"type" => "JSON"}) + + items = Map.get(json_response(conn, 200), "items") + + assert length(items) == 4 + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/block_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/block_transaction_controller_test.exs new file mode 100644 index 0000000..d76a2fc --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/block_transaction_controller_test.exs @@ -0,0 +1,204 @@ +defmodule BlockScoutWeb.BlockTransactionControllerTest do + use BlockScoutWeb.ConnCase + + import BlockScoutWeb.WebRouter.Helpers, only: [block_transaction_path: 3] + + describe "GET index/2" do + test "with invalid block number", %{conn: conn} do + conn = get(conn, block_transaction_path(conn, :index, "unknown")) + + assert html_response(conn, 404) + end + + test "with valid block number below the tip", %{conn: conn} do + insert(:block, number: 666) + + conn = get(conn, block_transaction_path(conn, :index, "1")) + + assert html_response(conn, 404) =~ "This block has not been processed yet." + end + + test "with valid block number above the tip", %{conn: conn} do + block = insert(:block) + + conn = get(conn, block_transaction_path(conn, :index, block.number + 1)) + + assert html_response(conn, 404) =~ "Easy Cowboy! This block does not exist yet!" + end + + test "returns transactions for the block", %{conn: conn} do + block = insert(:block) + + :transaction + |> insert() + |> with_block(block) + + :transaction + |> insert(to_address: nil) + |> with_block(block) + |> with_contract_creation(insert(:contract_address)) + + conn = get(conn, block_transaction_path(BlockScoutWeb.Endpoint, :index, block), %{type: "JSON"}) + + assert json_response(conn, 200) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.count(items) == 2 + end + + test "non-consensus block number without consensus blocks is treated as consensus number above tip", %{conn: conn} do + block = insert(:block, consensus: false) + + transaction = insert(:transaction) + insert(:transaction_fork, hash: transaction.hash, uncle_hash: block.hash) + + conn = get(conn, block_transaction_path(conn, :index, block.number)) + + assert_block_above_tip(conn) + end + + test "non-consensus block number above consensus block number is treated as consensus number above tip", %{ + conn: conn + } do + consensus_block = insert(:block, consensus: true, number: 1) + block = insert(:block, consensus: false, number: consensus_block.number + 1) + + transaction = insert(:transaction) + insert(:transaction_fork, hash: transaction.hash, uncle_hash: block.hash) + + conn = get(conn, block_transaction_path(conn, :index, block.number)) + + assert_block_above_tip(conn) + end + + test "returns transactions for consensus block hash", %{conn: conn} do + block = insert(:block, consensus: true) + + :transaction + |> insert() + |> with_block(block) + + conn = get(conn, block_transaction_path(BlockScoutWeb.Endpoint, :index, block), %{type: "JSON"}) + + assert json_response(conn, 200) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.count(items) == 1 + end + + test "does not return transactions for non-consensus block hash", %{conn: conn} do + block = insert(:block, consensus: false) + + transaction = insert(:transaction) + insert(:transaction_fork, hash: transaction.hash, uncle_hash: block.hash) + + conn = get(conn, block_transaction_path(BlockScoutWeb.Endpoint, :index, block), %{type: "JSON"}) + + assert json_response(conn, 200) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.empty?(items) + end + + test "does not return transactions for invalid block hash", %{conn: conn} do + conn = get(conn, block_transaction_path(conn, :index, "0x0")) + + assert html_response(conn, 404) + end + + test "with valid not-indexed hash", %{conn: conn} do + conn = get(conn, block_transaction_path(conn, :index, block_hash())) + + assert html_response(conn, 404) =~ "Block not found, please try again later." + end + + test "does not return unrelated transactions", %{conn: conn} do + insert(:transaction) + block = insert(:block) + + conn = get(conn, block_transaction_path(BlockScoutWeb.Endpoint, :index, block), %{type: "JSON"}) + + assert json_response(conn, 200) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.empty?(items) + end + + test "does not return related transactions without a block", %{conn: conn} do + block = insert(:block) + insert(:transaction) + + conn = get(conn, block_transaction_path(BlockScoutWeb.Endpoint, :index, block), %{type: "JSON"}) + + assert json_response(conn, 200) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.empty?(items) + end + + test "next_page_path exists if not on last page", %{conn: conn} do + block = insert(:block) + + 60 + |> insert_list(:transaction) + |> with_block(block) + + conn = get(conn, block_transaction_path(BlockScoutWeb.Endpoint, :index, block), %{type: "JSON"}) + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + assert next_page_path + end + + test "next_page_path is empty if on last page", %{conn: conn} do + block = insert(:block) + + :transaction + |> insert() + |> with_block(block) + + conn = get(conn, block_transaction_path(BlockScoutWeb.Endpoint, :index, block), %{type: "JSON"}) + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + refute next_page_path + end + + test "displays miner primary address name", %{conn: conn} do + miner_name = "POA Miner Pool" + %{address: miner_address} = insert(:address_name, name: miner_name, primary: true) + + block = insert(:block, miner: miner_address, miner_hash: nil) + + conn = get(conn, block_transaction_path(conn, :index, block)) + assert html_response(conn, 200) =~ miner_name + end + end + + defp assert_block_above_tip(conn) do + assert conn + |> html_response(404) + |> Floki.find(~S|.error-descr|) + |> Floki.text() + |> String.trim() == "Easy Cowboy! This block does not exist yet!" + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/chain/market_history_chart_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/chain/market_history_chart_controller_test.exs new file mode 100644 index 0000000..e295880 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/chain/market_history_chart_controller_test.exs @@ -0,0 +1,24 @@ +defmodule BlockScoutWeb.Chain.MarketHistoryChartControllerTest do + use BlockScoutWeb.ConnCase + + describe "GET show/2" do + test "returns error when not an ajax request" do + path = market_history_chart_path(BlockScoutWeb.Endpoint, :show) + + conn = get(build_conn(), path) + + assert conn.status == 422 + end + + test "returns ok when request is ajax" do + path = market_history_chart_path(BlockScoutWeb.Endpoint, :show) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert json_response(conn, 200) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/chain/transaction_history_chart_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/chain/transaction_history_chart_controller_test.exs new file mode 100644 index 0000000..7de3976 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/chain/transaction_history_chart_controller_test.exs @@ -0,0 +1,57 @@ +defmodule BlockScoutWeb.Chain.TransactionHistoryChartControllerTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.Chain.TransactionHistoryChartController + alias Explorer.Chain.Transaction.History.TransactionStats + alias Explorer.Repo + + describe "GET show/2" do + test "returns error when not an ajax request" do + path = transaction_history_chart_path(BlockScoutWeb.Endpoint, :show) + + conn = get(build_conn(), path) + + assert conn.status == 422 + end + + test "returns ok when request is ajax" do + path = transaction_history_chart_path(BlockScoutWeb.Endpoint, :show) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert json_response(conn, 200) + end + + test "returns appropriate json data" do + latest = Date.utc_today() + dts = [latest, Date.add(latest, -1), Date.add(latest, -2)] + + some_transaction_stats = [ + %{date: Enum.at(dts, 1), number_of_transactions: 20}, + %{date: Enum.at(dts, 2), number_of_transactions: 30} + ] + + {num, _} = Repo.insert_all(TransactionStats, some_transaction_stats) + assert num == 2 + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> TransactionHistoryChartController.show([]) + + # turn conn.resp_body into a map using JSON + response_data = Jason.decode!(conn.resp_body, keys: :atoms) + history_data = Jason.decode!(response_data.history_data, keys: :atoms) + + history_data_with_elixir_dates = + Enum.map(history_data, fn stat -> + Map.put(stat, :date, Date.from_iso8601!(stat.date)) + end) + + assert history_data_with_elixir_dates == some_transaction_stats + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs new file mode 100644 index 0000000..246ab7f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs @@ -0,0 +1,241 @@ +defmodule BlockScoutWeb.ChainControllerTest do + use BlockScoutWeb.ConnCase, + # ETS table is shared in `Explorer.Counters.AddressesCounter` + async: false + + import BlockScoutWeb.WebRouter.Helpers, only: [chain_path: 2, block_path: 3, transaction_path: 3, address_path: 3] + + alias Explorer.Chain.Block + alias Explorer.Counters.AddressesCounter + + 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()) + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + :ok + end + + describe "GET index/2" do + test "returns a welcome message", %{conn: conn} do + conn = get(conn, chain_path(BlockScoutWeb.Endpoint, :show)) + + assert(html_response(conn, 200) =~ "POA") + end + + test "returns a block" do + insert(:block, %{number: 23}) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get("/chain-blocks") + + response = json_response(conn, 200) + + assert(List.first(response["blocks"])["block_number"] == 23) + end + + test "excludes all but the most recent five blocks" do + old_block = insert(:block) + insert_list(5, :block) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get("/chain-blocks") + + response = json_response(conn, 200) + + refute(Enum.member?(response["blocks"], old_block)) + end + + test "displays miner primary address names" do + miner_name = "POA Miner Pool" + %{address: miner_address} = insert(:address_name, name: miner_name, primary: true) + + insert(:block, miner: miner_address, miner_hash: nil) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get("/chain-blocks") + + response = List.first(json_response(conn, 200)["blocks"]) + + assert response["chain_block_html"] =~ miner_name + end + end + + describe "GET token_autocomplete/2" do + test "finds matching tokens" do + insert(:token, name: "MaGiC") + insert(:token, name: "Evil") + + conn = + build_conn() + |> get("/token-autocomplete?q=magic") + + assert Enum.count(json_response(conn, 200)) == 1 + end + + test "finds two matching tokens" do + insert(:token, name: "MaGiC") + insert(:token, name: "magic") + + conn = + build_conn() + |> get("/token-autocomplete?q=magic") + + assert Enum.count(json_response(conn, 200)) == 2 + end + + test "finds verified contract" do + insert(:smart_contract, name: "SuperToken", contract_code_md5: "123") + + conn = + build_conn() + |> get("/token-autocomplete?q=sup") + + assert Enum.count(json_response(conn, 200)) == 1 + end + + test "finds verified contract and token" do + insert(:smart_contract, name: "MagicContract", contract_code_md5: "123") + insert(:token, name: "magicToken") + + conn = + build_conn() + |> get("/token-autocomplete?q=mag") + + assert Enum.count(json_response(conn, 200)) == 2 + end + + test "finds verified contracts and tokens" do + insert(:smart_contract, name: "something", contract_code_md5: "123") + insert(:smart_contract, name: "MagicContract", contract_code_md5: "123") + insert(:token, name: "Magic3") + insert(:smart_contract, name: "magicContract2", contract_code_md5: "123") + insert(:token, name: "magicToken") + insert(:token, name: "OneMoreToken") + + conn = + build_conn() + |> get("/token-autocomplete?q=mag") + + assert Enum.count(json_response(conn, 200)) == 4 + end + + test "find by several words" do + insert(:token, name: "first Token") + insert(:token, name: "second Token") + + conn = + build_conn() + |> get("/token-autocomplete?q=fir+tok") + + assert Enum.count(json_response(conn, 200)) == 1 + end + + test "find by empty query" do + insert(:token, name: "MaGiCt0k3n") + insert(:smart_contract, name: "MagicContract", contract_code_md5: "123") + + conn = + build_conn() + |> get("/token-autocomplete?q=") + + assert Enum.count(json_response(conn, 200)) == 0 + end + + test "find by non-latin characters" do + insert(:token, name: "someToken") + + conn = + build_conn() + |> get("/token-autocomplete?q=%E0%B8%B5%E0%B8%AB") + + assert Enum.count(json_response(conn, 200)) == 0 + end + end + + describe "GET search/2" do + test "finds a consensus block by block number", %{conn: conn} do + insert(:block, number: 37) + conn = get(conn, "/search?q=37") + + assert redirected_to(conn) == block_path(conn, :show, "37") + end + + test "redirects to search results page even for searching non-consensus block by number", %{conn: conn} do + %Block{number: number} = insert(:block, consensus: false) + + conn = get(conn, "/search?q=#{number}") + + assert conn.status == 302 + end + + test "finds non-consensus block by hash", %{conn: conn} do + %Block{hash: hash} = insert(:block, consensus: false) + + conn = get(conn, "/search?q=#{hash}") + + assert redirected_to(conn) == block_path(conn, :show, hash) + end + + test "finds a transaction by hash", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + conn = get(conn, "/search?q=#{to_string(transaction.hash)}") + + assert redirected_to(conn) == transaction_path(conn, :show, transaction) + end + + test "finds a transaction by hash when there are not 0x prefix", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + "0x" <> non_prefix_hash = to_string(transaction.hash) + + conn = get(conn, "/search?q=#{to_string(non_prefix_hash)}") + + assert redirected_to(conn) == transaction_path(conn, :show, transaction) + end + + test "finds an address by hash", %{conn: conn} do + address = insert(:address) + conn = get(conn, "/search?q=#{to_string(address.hash)}") + + assert redirected_to(conn) == address_path(conn, :show, address) + end + + test "finds an address by hash when there are extra spaces", %{conn: conn} do + address = insert(:address) + conn = get(conn, "/search?q=#{to_string(address.hash)}") + + assert redirected_to(conn) == address_path(conn, :show, address) + end + + test "finds an address by hash when there are not 0x prefix", %{conn: conn} do + address = insert(:address) + "0x" <> non_prefix_hash = to_string(address.hash) + + conn = get(conn, "/search?q=#{to_string(non_prefix_hash)}") + + assert redirected_to(conn) == address_path(conn, :show, address) + end + + test "redirects to result page when it finds nothing", %{conn: conn} do + conn = get(conn, "/search?q=zaphod") + assert conn.status == 302 + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/page_not_found_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/page_not_found_controller_test.exs new file mode 100644 index 0000000..8540358 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/page_not_found_controller_test.exs @@ -0,0 +1,11 @@ +defmodule BlockScoutWeb.PageNotFoundControllerTest do + use BlockScoutWeb.ConnCase + + describe "GET index/2" do + test "returns 404 status", %{conn: conn} do + conn = get(conn, "/wrong", %{}) + + assert html_response(conn, 404) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs new file mode 100644 index 0000000..b19bec2 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs @@ -0,0 +1,84 @@ +defmodule BlockScoutWeb.PendingTransactionControllerTest do + use BlockScoutWeb.ConnCase + alias Explorer.Chain.{Hash, Transaction} + + import BlockScoutWeb.WebRouter.Helpers, only: [pending_transaction_path: 2, pending_transaction_path: 3] + + describe "GET index/2" do + test "returns no transactions that are in a block", %{conn: conn} do + :transaction + |> insert() + |> with_block() + + conn = get(conn, pending_transaction_path(BlockScoutWeb.Endpoint, :index, %{"type" => "JSON"})) + + assert conn |> json_response(200) |> Map.get("items") |> Enum.empty?() + end + + test "returns pending transactions", %{conn: conn} do + transaction = insert(:transaction) + + conn = get(conn, pending_transaction_path(BlockScoutWeb.Endpoint, :index, %{"type" => "JSON"})) + + assert hd(json_response(conn, 200)["items"]) =~ to_string(transaction.hash) + end + + test "does not show dropped/replaced transactions", %{conn: conn} do + transaction = insert(:transaction) + + dropped_replaced = + :transaction + |> insert(status: 0, error: "dropped/replaced") + |> with_block() + + conn = get(conn, pending_transaction_path(BlockScoutWeb.Endpoint, :index, %{"type" => "JSON"})) + + assert hd(json_response(conn, 200)["items"]) =~ to_string(transaction.hash) + refute hd(json_response(conn, 200)["items"]) =~ to_string(dropped_replaced.hash) + end + + test "works when there are no transactions", %{conn: conn} do + conn = get(conn, pending_transaction_path(conn, :index)) + + assert html_response(conn, 200) + end + + test "returns next page of results based on last seen pending transaction", %{conn: conn} do + second_page_hashes = + 50 + |> insert_list(:transaction) + |> Enum.map(&to_string(&1.hash)) + + %Transaction{inserted_at: inserted_at, hash: hash} = insert(:transaction) + + conn = + get(conn, pending_transaction_path(BlockScoutWeb.Endpoint, :index), %{ + "type" => "JSON", + "inserted_at" => DateTime.to_iso8601(inserted_at), + "hash" => Hash.to_string(hash) + }) + + {:ok, %{"items" => pending_transactions}} = Poison.decode(conn.resp_body) + + assert length(pending_transactions) == length(second_page_hashes) + end + + test "next_page_path exist if not on last page", %{conn: conn} do + 60 + |> insert_list(:transaction) + |> Enum.fetch!(10) + + conn = get(conn, pending_transaction_path(BlockScoutWeb.Endpoint, :index, %{"type" => "JSON"})) + + assert json_response(conn, 200)["next_page_path"] + end + + test "next_page_path is empty if on last page", %{conn: conn} do + insert(:transaction) + + conn = get(conn, pending_transaction_path(BlockScoutWeb.Endpoint, :index, %{"type" => "JSON"})) + + refute json_response(conn, 200)["next_page_path"] + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/recent_transactions_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/recent_transactions_controller_test.exs new file mode 100644 index 0000000..0a2267f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/recent_transactions_controller_test.exs @@ -0,0 +1,54 @@ +defmodule BlockScoutWeb.RecentTransactionsControllerTest do + use BlockScoutWeb.ConnCase + + import BlockScoutWeb.WebRouter.Helpers, only: [recent_transactions_path: 2] + + alias Explorer.Chain.Hash + + describe "GET index/2" do + test "returns a transaction", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + conn = + conn + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(recent_transactions_path(conn, :index)) + + assert response = json_response(conn, 200)["transactions"] + + response_hashes = Enum.map(response, & &1["transaction_hash"]) + + assert Enum.member?(response_hashes, Hash.to_string(transaction.hash)) + end + + test "only returns transactions with an associated block", %{conn: conn} do + associated = + :transaction + |> insert() + |> with_block() + + unassociated = insert(:transaction) + + conn = + conn + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(recent_transactions_path(conn, :index)) + + assert response = json_response(conn, 200)["transactions"] + + response_hashes = Enum.map(response, & &1["transaction_hash"]) + + assert Enum.member?(response_hashes, Hash.to_string(associated.hash)) + refute Enum.member?(response_hashes, Hash.to_string(unassociated.hash)) + end + + test "only responds to ajax requests", %{conn: conn} do + conn = get(conn, recent_transactions_path(conn, :index)) + + assert conn.status == 422 + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs new file mode 100644 index 0000000..1714841 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -0,0 +1,351 @@ +defmodule BlockScoutWeb.SmartContractControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + alias Explorer.Chain.{Address, Hash} + alias Explorer.Factory + + setup :verify_on_exit! + + describe "GET index/3" do + test "returns not found for nonexistent address" do + nonexistent_address_hash = Hash.to_string(Factory.address_hash()) + path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: nonexistent_address_hash) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert html_response(conn, 404) + end + + test "error for invalid address" do + path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00", type: :regular, action: :read) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 422 + end + + test "only responds to ajax requests", %{conn: conn} do + smart_contract = insert(:smart_contract, contract_code_md5: "123") + + path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: smart_contract.address_hash) + + conn = get(conn, path) + + assert conn.status == 404 + end + + test "lists the smart contract read only functions" do + token_contract_address = insert(:contract_address) + + insert(:smart_contract, address_hash: token_contract_address.hash, contract_code_md5: "123") + + blockchain_get_code_mock() + blockchain_get_function_mock() + + path = + smart_contract_path(BlockScoutWeb.Endpoint, :index, + hash: token_contract_address.hash, + type: :regular, + action: :read + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 200 + refute conn.assigns.read_only_functions == [] + end + + test "lists [] proxy read only functions if no verified implementation" do + token_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: token_contract_address.hash, + abi: [ + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "implementation", + "inputs" => [], + "constant" => true + } + ], + contract_code_md5: "123" + ) + + blockchain_get_code_mock() + + path = + smart_contract_path(BlockScoutWeb.Endpoint, :index, + hash: token_contract_address.hash, + type: :proxy, + action: :read + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 200 + assert conn.assigns.read_only_functions == [] + end + + test "lists [] proxy read only functions if no verified eip-1967 implementation" do + token_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: token_contract_address.hash, + abi: [ + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [], + "constant" => false + } + ], + contract_code_md5: "123" + ) + + blockchain_get_code_mock() + blockchain_get_implementation_mock() + + path = + smart_contract_path(BlockScoutWeb.Endpoint, :index, + hash: token_contract_address.hash, + type: :proxy, + action: :read + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 200 + assert conn.assigns.read_only_functions == [] + end + + test "lists [] proxy read only functions if no verified eip-1967 implementation and eth_getStorageAt returns not normalized address hash" do + token_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: token_contract_address.hash, + abi: [ + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [], + "constant" => false + } + ], + contract_code_md5: "123" + ) + + blockchain_get_code_mock() + blockchain_get_implementation_mock_2() + + path = + smart_contract_path(BlockScoutWeb.Endpoint, :index, + hash: token_contract_address.hash, + type: :proxy, + action: :read + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 200 + assert conn.assigns.read_only_functions == [] + end + end + + describe "GET show/3" do + test "returns not found for nonexistent address" do + nonexistent_address_hash = Address.checksum(Hash.to_string(Factory.address_hash())) + + path = + smart_contract_path( + BlockScoutWeb.Endpoint, + :show, + nonexistent_address_hash, + function_name: "get", + args: [] + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert html_response(conn, 404) + end + + test "error for invalid address" do + path = + smart_contract_path( + BlockScoutWeb.Endpoint, + :show, + "0x00", + function_name: "get", + args: [] + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 422 + end + + test "only responds to ajax requests", %{conn: conn} do + smart_contract = insert(:smart_contract, contract_code_md5: "123") + + path = + smart_contract_path( + BlockScoutWeb.Endpoint, + :show, + Address.checksum(smart_contract.address_hash), + function_name: "get", + args: [] + ) + + conn = get(conn, path) + + assert conn.status == 404 + end + + test "fetch the function value from the blockchain" do + address = insert(:contract_address) + smart_contract = insert(:smart_contract, address_hash: address.hash, contract_code_md5: "123") + + blockchain_get_code_mock() + blockchain_get_function_mock() + + path = + smart_contract_path( + BlockScoutWeb.Endpoint, + :show, + Address.checksum(smart_contract.address_hash), + function_name: "get", + method_id: "6d4ce63c", + args_count: 0, + args: [] + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 200 + + assert %{ + function_name: "get", + layout: false, + outputs: [%{"type" => "uint256", "value" => 0}] + } = conn.assigns + end + end + + defp blockchain_get_function_mock do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000000"}]} + end + ) + end + + defp blockchain_get_code_mock do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} + end + ) + end + + defp blockchain_get_implementation_mock do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn %{id: _, method: _, params: [_, _, _]}, _options -> + {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + end + ) + end + + defp blockchain_get_implementation_mock_2 do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn %{id: _, method: _, params: [_, _, _]}, _options -> + {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + end + ) + end + + def get_eip1967_implementation 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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/holder_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/holder_controller_test.exs new file mode 100644 index 0000000..45c23d6 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/holder_controller_test.exs @@ -0,0 +1,93 @@ +defmodule BlockScoutWeb.Tokens.HolderControllerTest do + use BlockScoutWeb.ConnCase, async: true + + alias Explorer.Chain.{Address, Hash} + + describe "GET index/3" do + test "with invalid address hash", %{conn: conn} do + conn = get(conn, token_holder_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with a token that doesn't exist", %{conn: conn} do + address = build(:address) + conn = get(conn, token_holder_path(BlockScoutWeb.Endpoint, :index, address.hash)) + + assert html_response(conn, 404) + end + + test "successfully renders the page", %{conn: conn} do + token = insert(:token) + + insert_list( + 2, + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash + ) + + conn = + get( + conn, + token_holder_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash) + ) + + assert html_response(conn, 200) + end + + test "returns next page of results based on last seen token balance", %{conn: conn} do + contract_address = build(:contract_address, hash: "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493") + token = insert(:token, contract_address: contract_address) + + second_page_token_balances = + 1..50 + |> Enum.map( + &insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + value: &1 + 1000 + ) + ) + + token_balance = + insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + value: 50000 + ) + + conn = + get(conn, token_holder_path(conn, :index, token.contract_address_hash), %{ + "value" => Decimal.to_integer(token_balance.value), + "address_hash" => Hash.to_string(token_balance.address_hash), + "type" => "JSON" + }) + + token_balance_tiles = json_response(conn, 200)["items"] + + assert Enum.all?(second_page_token_balances, fn token_balance -> + Enum.any?(token_balance_tiles, fn tile -> + String.contains?(tile, Address.checksum(token_balance.address_hash)) + end) + end) + end + + test "next_page_params exists if not on last page", %{conn: conn} do + contract_address = build(:contract_address, hash: "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493") + token = insert(:token, contract_address: contract_address) + + Enum.each( + 1..51, + &insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + value: &1 + 1000 + ) + ) + + conn = get(conn, token_holder_path(conn, :index, token.contract_address_hash, %{"type" => "JSON"})) + + assert json_response(conn, 200)["next_page_path"] + end + 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 0000000..8f4a49c --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs @@ -0,0 +1,30 @@ +defmodule BlockScoutWeb.Tokens.Instance.TransferControllerTest do + use BlockScoutWeb.ConnCase, async: false + + describe "GET token-transfers/2" do + test "fetches the instance", %{conn: conn} do + contract_address = insert(:address) + + insert(:token, contract_address: contract_address) + + contract_address_hash = 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 new file mode 100644 index 0000000..7ced36d --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.Tokens.InstanceControllerTest do + use BlockScoutWeb.ConnCase, async: false + + describe "GET show/2" do + test "redirects with valid params", %{conn: conn} do + contract_address = insert(:address) + + insert(:token, contract_address: contract_address) + + token_id = 10 + + insert(:token_transfer, + from_address: contract_address, + token_contract_address: contract_address, + token_id: token_id + ) + + conn = get(conn, token_instance_path(BlockScoutWeb.Endpoint, :show, to_string(contract_address.hash), token_id)) + + assert conn.status == 302 + end + + test "works for ERC-1155 tokens", %{conn: conn} do + contract_address = insert(:address) + + insert(:token, contract_address: contract_address) + + token_id = 10 + + insert(:token_transfer, + from_address: contract_address, + token_contract_address: contract_address, + token_id: nil, + token_ids: [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 new file mode 100644 index 0000000..e1591a4 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs @@ -0,0 +1,141 @@ +defmodule BlockScoutWeb.Tokens.InventoryControllerTest do + use BlockScoutWeb.ConnCase, async: false + + describe "GET index/3" do + test "with invalid address hash", %{conn: conn} do + conn = get(conn, token_inventory_path(conn, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with a token that doesn't exist", %{conn: conn} do + address = build(:address) + conn = get(conn, token_inventory_path(conn, :index, address.hash)) + + assert html_response(conn, 404) + end + + test "successfully renders the page", %{conn: conn} do + token_contract_address = insert(:contract_address) + token = insert(:token, type: "ERC-721", contract_address: token_contract_address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :token_transfer, + transaction: transaction, + token_contract_address: token_contract_address, + token: token + ) + + conn = + get( + conn, + token_inventory_path(conn, :index, token_contract_address.hash) + ) + + assert html_response(conn, 200) + end + + test "returns next page of results based on last seen token balance", %{conn: conn} do + token = insert(:token, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block() + + second_page_token_balances = + Enum.map(1..50, fn i -> + insert( + :token_transfer, + transaction: transaction, + token_contract_address: token.contract_address, + token: token, + token_id: i + 1000 + ) + + insert( + :token_instance, + token_contract_address_hash: token.contract_address.hash, + token_id: i + 1000 + ) + end) + + conn = + get(conn, token_inventory_path(conn, :index, token.contract_address_hash), %{ + "token_id" => "999", + "type" => "JSON" + }) + + conn = get(conn, token_inventory_path(conn, :index, token.contract_address_hash), %{type: "JSON"}) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.count(items) == Enum.count(second_page_token_balances) + end + + test "next_page_path exists if not on last page", %{conn: conn} do + token = insert(:token, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block() + + Enum.each(1..51, fn i -> + insert( + :token_transfer, + transaction: transaction, + token_contract_address: token.contract_address, + token: token, + token_id: i + 1000 + ) + + insert( + :token_instance, + token_contract_address_hash: token.contract_address.hash, + token_id: i + 1000 + ) + end) + + conn = get(conn, token_inventory_path(conn, :index, token.contract_address_hash), %{type: "JSON"}) + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + assert next_page_path + end + + test "next_page_path is empty if on last page", %{conn: conn} do + token = insert(:token, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :token_transfer, + transaction: transaction, + token_contract_address: token.contract_address, + token: token, + token_id: 1000 + ) + + conn = get(conn, token_inventory_path(conn, :index, token.contract_address_hash), %{type: "JSON"}) + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + refute next_page_path + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs new file mode 100644 index 0000000..ab5ba16 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs @@ -0,0 +1,104 @@ +defmodule BlockScoutWeb.Tokens.ContractControllerTest do + use BlockScoutWeb.ConnCase, async: false + + import Mox + + describe "GET index/3" do + test "with invalid address hash", %{conn: conn} do + conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with unverified address hash returns not found", %{conn: conn} do + address = insert(:address) + + token = insert(:token, contract_address: address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction, + token_contract_address: address, + token: token + ) + + conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash)) + + assert html_response(conn, 404) + end + + test "successfully renders the page when the token is a verified smart contract", %{conn: conn} do + token_contract_address = insert(:contract_address) + + insert(:smart_contract, address_hash: token_contract_address.hash, contract_code_md5: "123") + + token = insert(:token, contract_address: token_contract_address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token + ) + + get_eip1967_implementation() + + conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash)) + + assert html_response(conn, 200) + assert token.contract_address_hash == conn.assigns.token.contract_address_hash + end + end + + def get_eip1967_implementation 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 +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/token_controller_test.exs new file mode 100644 index 0000000..ab5d93f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/token_controller_test.exs @@ -0,0 +1,39 @@ +defmodule BlockScoutWeb.Tokens.TokenControllerTest do + use BlockScoutWeb.ConnCase, async: true + + alias Explorer.Chain.Address + + describe "GET show/2" do + test "redirects to transfers page", %{conn: conn} do + contract_address = insert(:address) + + conn = get(conn, token_path(conn, :show, Address.checksum(contract_address.hash))) + + assert conn.status == 302 + end + end + + # describe "GET token-counters/2" do + # # flaky test + # test "returns token counters", %{conn: conn} do + # contract_address = insert(:address) + + # insert(:token, contract_address: contract_address) + + # token_id = 10 + + # insert(:token_transfer, + # from_address: contract_address, + # token_contract_address: contract_address, + # token_id: token_id + # ) + + # conn = get(conn, "/token-counters", %{"id" => Address.checksum(contract_address.hash)}) + + # assert conn.status == 200 + # {:ok, response} = Jason.decode(conn.resp_body) + + # assert %{"token_holder_count" => 0, "transfer_count" => 1} == response + # end + # end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs new file mode 100644 index 0000000..c94d095 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs @@ -0,0 +1,137 @@ +defmodule BlockScoutWeb.TransactionControllerTest do + use BlockScoutWeb.ConnCase + + import BlockScoutWeb.WebRouter.Helpers, + only: [transaction_path: 3] + + alias Explorer.Chain.Transaction + + describe "GET index/2" do + test "returns a collated transactions", %{conn: conn} do + :transaction + |> insert() + |> with_block() + + conn = get(conn, transaction_path(conn, :index, %{"type" => "JSON"})) + + transactions_html = + conn + |> json_response(200) + |> Map.get("items") + + assert length(transactions_html) == 1 + end + + test "returns a count of transactions", %{conn: conn} do + :transaction + |> insert() + |> with_block() + + conn = get(conn, "/txs") + + assert is_integer(conn.assigns.transaction_estimated_count) + end + + test "excludes pending transactions", %{conn: conn} do + %Transaction{hash: transaction_hash} = + :transaction + |> insert() + |> with_block() + + %Transaction{hash: pending_transaction_hash} = insert(:transaction) + + conn = get(conn, transaction_path(conn, :index, %{"type" => "JSON"})) + + transactions_html = + conn + |> json_response(200) + |> Map.get("items") + + assert Enum.any?(transactions_html, &String.contains?(&1, to_string(transaction_hash))) + refute Enum.any?(transactions_html, &String.contains?(&1, to_string(pending_transaction_hash))) + end + + test "returns next page of results based on last seen transaction", %{conn: conn} do + second_page_hashes = + 50 + |> insert_list(:transaction) + |> with_block() + |> Enum.map(&to_string(&1.hash)) + + %Transaction{block_number: block_number, index: index} = + :transaction + |> insert() + |> with_block() + + conn = + get( + conn, + transaction_path(conn, :index, %{ + "type" => "JSON", + "block_number" => Integer.to_string(block_number), + "index" => Integer.to_string(index) + }) + ) + + transactions_html = + conn + |> json_response(200) + |> Map.get("items") + + assert length(second_page_hashes) == length(transactions_html) + end + + test "next_page_params exist if not on last page", %{conn: conn} do + address = insert(:address) + block = insert(:block) + + 60 + |> insert_list(:transaction, from_address: address) + |> with_block(block) + + conn = get(conn, transaction_path(conn, :index, %{"type" => "JSON"})) + + assert conn |> json_response(200) |> Map.get("next_page_params") + end + + test "next_page_params are empty if on last page", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + conn = get(conn, transaction_path(conn, :index, %{"type" => "JSON"})) + + refute conn |> json_response(200) |> Map.get("next_page_path") + end + + test "works when there are no transactions", %{conn: conn} do + conn = get(conn, "/txs") + + assert html_response(conn, 200) + end + end + + describe "GET show/3" do + test "responds with 404 with the transaction missing", %{conn: conn} do + hash = transaction_hash() + conn = get(conn, transaction_path(BlockScoutWeb.Endpoint, :show, hash)) + + assert html_response(conn, 404) + end + + test "responds with 422 when the hash is invalid", %{conn: conn} do + conn = get(conn, transaction_path(BlockScoutWeb.Endpoint, :show, "wrong")) + + assert html_response(conn, 422) + end + + test "no redirect from tx page", %{conn: conn} do + transaction = insert(:transaction) + conn = get(conn, transaction_path(BlockScoutWeb.Endpoint, :show, transaction)) + + assert html_response(conn, 200) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs new file mode 100644 index 0000000..4969db3 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs @@ -0,0 +1,231 @@ +defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + import BlockScoutWeb.WebRouter.Helpers, only: [transaction_internal_transaction_path: 3] + + alias Explorer.Chain.InternalTransaction + alias Explorer.ExchangeRates.Token + + describe "GET index/3" do + test "with missing transaction", %{conn: conn} do + hash = transaction_hash() + conn = get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, hash)) + + assert html_response(conn, 404) + end + + test "with invalid transaction hash", %{conn: conn} do + conn = get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, "nope")) + + assert html_response(conn, 422) + end + + test "includes transaction data", %{conn: conn} do + block = insert(:block, %{number: 777}) + + transaction = + :transaction + |> insert() + |> with_block(block) + + conn = get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) + + assert html_response(conn, 200) + assert conn.assigns.transaction.hash == transaction.hash + end + + test "includes internal transactions for the transaction", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + insert(:internal_transaction, + transaction: transaction, + index: 0, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 0 + ) + + insert(:internal_transaction, + transaction: transaction, + index: 1, + transaction_index: transaction.index, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 1 + ) + + path = transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash) + + conn = get(conn, path, %{type: "JSON"}) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert json_response(conn, 200) + + # excluding of internal transactions with type=call and index=0 + assert Enum.count(items) == 1 + end + + test "includes USD exchange rate value for address in assigns", %{conn: conn} do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> + {:ok, "100"} + end) + + transaction = insert(:transaction) + + conn = get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) + + assert %Token{} = conn.assigns.exchange_rate + end + + test "with no to_address_hash overview contains contract create address", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + :transaction + |> insert(to_address: nil) + |> with_contract_creation(contract_address) + |> with_block(insert(:block, number: 7000)) + + internal_transaction = + :internal_transaction_create + |> insert( + transaction: transaction, + index: 0, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 0 + ) + |> with_contract_creation(contract_address) + + conn = + get( + conn, + transaction_internal_transaction_path( + BlockScoutWeb.Endpoint, + :index, + internal_transaction.transaction_hash + ) + ) + + refute is_nil(conn.assigns.transaction.created_contract_address_hash) + end + + test "returns next page of results based on last seen internal transaction", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 7000)) + + %InternalTransaction{index: index} = + insert(:internal_transaction, + transaction: transaction, + index: 0, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 0 + ) + + second_page_indexes = + 1..50 + |> Enum.map(fn index -> + insert(:internal_transaction, + transaction: transaction, + index: index, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: index + ) + end) + |> Enum.map(& &1.index) + + conn = + get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash), %{ + "index" => Integer.to_string(index), + "type" => "JSON" + }) + + {:ok, %{"items" => items}} = + conn.resp_body + |> Poison.decode() + + assert Enum.count(items) == Enum.count(second_page_indexes) + end + + test "next_page_path exists if not on last page", %{conn: conn} do + block = insert(:block, number: 7000) + + transaction = + :transaction + |> insert() + |> with_block(block) + + 1..60 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction, + index: index, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: index + ) + end) + + conn = + get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash), %{ + type: "JSON" + }) + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + assert next_page_path + end + + test "next_page_path is empty if on last page", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 7000)) + + 1..2 + |> Enum.map(fn index -> + insert( + :internal_transaction, + transaction: transaction, + index: index, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: index + ) + end) + + conn = + get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash), %{ + type: "JSON" + }) + + {:ok, %{"next_page_path" => next_page_path}} = + conn.resp_body + |> Poison.decode() + + refute next_page_path + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs new file mode 100644 index 0000000..fc9ced3 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs @@ -0,0 +1,188 @@ +defmodule BlockScoutWeb.TransactionLogControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + import BlockScoutWeb.WebRouter.Helpers, only: [transaction_log_path: 3] + + alias Explorer.Chain.Address + alias Explorer.ExchangeRates.Token + + describe "GET index/2" do + test "with invalid transaction hash", %{conn: conn} do + conn = get(conn, transaction_log_path(conn, :index, "invalid_transaction_string")) + + assert html_response(conn, 422) + end + + test "with valid transaction hash without transaction", %{conn: conn} do + conn = + get( + conn, + transaction_log_path(conn, :index, "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6") + ) + + assert html_response(conn, 404) + end + + test "returns logs for the transaction", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + address = insert(:address) + + insert(:log, + address: address, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"}) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + first_log = List.first(items) + + assert String.contains?(first_log, Address.checksum(address.hash)) + end + + test "returns logs for the transaction with nil to_address", %{conn: conn} do + transaction = + :transaction + |> insert(to_address: nil) + |> with_block() + + address = insert(:address) + + insert(:log, + address: address, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"}) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + first_log = List.first(items) + + assert String.contains?(first_log, Address.checksum(address.hash)) + end + + test "assigns no logs when there are none", %{conn: conn} do + transaction = insert(:transaction) + path = transaction_log_path(conn, :index, transaction) + + conn = get(conn, path, %{type: "JSON"}) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + + assert Enum.empty?(items) + end + + test "returns next page of results based on last seen transaction log", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + log = + insert(:log, + transaction: transaction, + index: 1, + block: transaction.block, + block_number: transaction.block_number + ) + + second_page_indexes = + 2..51 + |> Enum.map(fn index -> + insert(:log, + transaction: transaction, + index: index, + block: transaction.block, + block_number: transaction.block_number + ) + end) + |> Enum.map(& &1.index) + + conn = + get(conn, transaction_log_path(conn, :index, transaction), %{ + "index" => Integer.to_string(log.index), + "type" => "JSON" + }) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + + assert Enum.count(items) == Enum.count(second_page_indexes) + end + + test "next_page_path exists if not on last page", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + 1..60 + |> Enum.map(fn index -> + insert(:log, + transaction: transaction, + index: index, + block: transaction.block, + block_number: transaction.block_number + ) + end) + + conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"}) + + {:ok, %{"next_page_path" => path}} = conn.resp_body |> Poison.decode() + + assert path + end + + test "next_page_path is empty if on last page", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"}) + + {:ok, %{"next_page_path" => path}} = conn.resp_body |> Poison.decode() + + refute path + end + end + + test "includes USD exchange rate value for address in assigns", %{conn: conn} do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> + {:ok, "100"} + end) + + transaction = insert(:transaction) + + conn = get(conn, transaction_log_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) + + assert %Token{} = conn.assigns.exchange_rate + end + + test "loads for transactions that created a contract", %{conn: conn} do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> + {:ok, "100"} + end) + + contract_address = insert(:contract_address) + + transaction = + :transaction + |> insert(to_address: nil) + |> with_contract_creation(contract_address) + + conn = get(conn, transaction_log_path(conn, :index, transaction)) + assert html_response(conn, 200) + end +end 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 0000000..a4adb8f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs @@ -0,0 +1,187 @@ +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 + alias EthereumJSONRPC.Blocks + + 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 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/transaction_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs new file mode 100644 index 0000000..6b789a2 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs @@ -0,0 +1,217 @@ +defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + import BlockScoutWeb.WebRouter.Helpers, only: [transaction_token_transfer_path: 3] + + alias Explorer.ExchangeRates.Token + + describe "GET index/3" do + test "load token transfers", %{conn: conn} do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> + {:ok, "100"} + end) + + transaction = insert(:transaction) + token_transfer = insert(:token_transfer, transaction: transaction) + + conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) + + assigned_token_transfer = List.first(conn.assigns.transaction.token_transfers) + + assert {assigned_token_transfer.transaction_hash, assigned_token_transfer.log_index} == + {token_transfer.transaction_hash, token_transfer.log_index} + end + + test "with missing transaction", %{conn: conn} do + hash = transaction_hash() + conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, hash)) + + assert html_response(conn, 404) + end + + test "with invalid transaction hash", %{conn: conn} do + conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, "nope")) + + assert html_response(conn, 422) + end + + test "includes transaction data", %{conn: conn} do + block = insert(:block, %{number: 777}) + + transaction = + :transaction + |> insert() + |> with_block(block) + + conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) + + assert html_response(conn, 200) + assert conn.assigns.transaction.hash == transaction.hash + end + + test "includes token transfers for the transaction", %{conn: conn} do + transaction = insert(:transaction) |> with_block() + + insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number) + + insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number) + + path = transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash) + + conn = get(conn, path, %{type: "JSON"}) + + assert json_response(conn, 200) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + + assert Enum.count(items) == 2 + end + + test "includes USD exchange rate value for address in assigns", %{conn: conn} do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> + {:ok, "100"} + end) + + transaction = insert(:transaction) + + conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) + + assert %Token{} = conn.assigns.exchange_rate + end + + test "returns next page of results based on last seen token transfer", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + insert(:token_transfer, transaction: transaction, block_number: 1000, log_index: 1) + + Enum.each(2..5, fn item -> + insert(:token_transfer, transaction: transaction, block_number: item + 1001, log_index: item + 1) + end) + + conn = + get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash), %{ + "block_number" => "1000", + "index" => "1", + "type" => "JSON" + }) + + {:ok, %{"items" => items}} = conn.resp_body |> Poison.decode() + + refute Enum.count(items) == 3 + end + + test "next_page_path exists if not on last page", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + 1..51 + |> Enum.map(fn log_index -> + insert( + :token_transfer, + transaction: transaction, + log_index: log_index, + block: transaction.block, + block_number: transaction.block_number + ) + end) + + conn = + get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash), %{type: "JSON"}) + + {:ok, %{"next_page_path" => path}} = conn.resp_body |> Poison.decode() + + assert path + end + + test "next_page_path is empty if on last page", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + 1..2 + |> Enum.map(fn log_index -> + insert( + :token_transfer, + transaction: transaction, + log_index: log_index, + block_number: transaction.block_number, + block: transaction.block + ) + end) + + conn = + get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash), %{type: "JSON"}) + + {:ok, %{"next_page_path" => path}} = conn.resp_body |> Poison.decode() + + refute path + end + + test "preloads to_address smart contract verified", %{conn: conn} do + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end + ) + |> expect( + :json_rpc, + fn %{ + id: _id, + 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) + |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> + {:ok, "100"} + end) + + transaction = insert(:transaction_to_verified_contract) + + conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) + + assert html_response(conn, 200) + assert conn.assigns.transaction.hash == transaction.hash + assert conn.assigns.transaction.to_address.smart_contract != nil + 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 0000000..dd5327c --- /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/address_contract_verification_test.exs b/apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs new file mode 100644 index 0000000..1a82a6d --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs @@ -0,0 +1,70 @@ +defmodule BlockScoutWeb.AddressContractVerificationTest do + use BlockScoutWeb.FeatureCase, async: false + + alias BlockScoutWeb.{AddressContractPage, ContractVerifyPage} + alias Explorer.Factory + alias Plug.Conn + + setup do + bypass = Bypass.open() + + Application.put_env(:explorer, :solc_bin_api_url, "http://localhost:#{bypass.port}") + + {:ok, bypass: bypass} + end + + # wallaby with chrome headles always fails this test + @tag :skip + test "users validates smart contract", %{session: session, bypass: bypass} do + Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, solc_bin_versions()) end) + + %{name: name, source_code: source_code, bytecode: bytecode, version: version} = Factory.contract_code_info() + + transaction = :transaction |> insert() |> with_block() + address = insert(:address, contract_code: bytecode) + + insert( + :internal_transaction_create, + created_contract_address: address, + created_contract_code: bytecode, + index: 0, + transaction: transaction + ) + + session + |> AddressContractPage.visit_page(address) + |> AddressContractPage.click_verify_and_publish() + |> ContractVerifyPage.fill_form(%{ + contract_name: name, + version: version, + optimization: false, + source_code: source_code, + evm_version: "byzantium" + }) + |> ContractVerifyPage.verify_and_publish() + + assert AddressContractPage.on_page?(session, address) + end + + test "with invalid data shows error messages", %{session: session, bypass: bypass} do + Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, solc_bin_versions()) end) + + address = insert(:address) + + session + |> ContractVerifyPage.visit_page(address) + |> ContractVerifyPage.fill_form(%{ + contract_name: "name", + version: "default", + optimization: "true", + source_code: "", + evm_version: "byzantium" + }) + |> ContractVerifyPage.verify_and_publish() + |> ContractVerifyPage.has_message?("There was an error validating your contract, please try again.") + end + + defp solc_bin_versions do + File.read!("./test/support/fixture/smart_contract/solc_bin.json") + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex new file mode 100644 index 0000000..2930e35 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex @@ -0,0 +1,23 @@ +defmodule BlockScoutWeb.AddressContractPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1] + + def on_page?(session, address) do + current_path(session) =~ address_contract_path(address) + end + + def click_verify_and_publish(session) do + click(session, css("[data-test='verify_and_publish']")) + end + + def visit_page(session, address) do + visit(session, address_contract_path(address)) + end + + defp address_contract_path(address) do + "/address/#{address.hash}/contracts" + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex new file mode 100644 index 0000000..99b66dd --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex @@ -0,0 +1,178 @@ +defmodule BlockScoutWeb.AddressPage do + @moduledoc false + + use Wallaby.DSL + import Wallaby.Query, only: [css: 1, css: 2] + alias Explorer.Chain.{Address, InternalTransaction, Hash, Transaction, Token} + + def apply_filter(session, direction) do + session + |> click(css("[data-test='filter_dropdown']", text: "Filter: All")) + |> click(css("[data-test='filter_option']", text: direction)) + end + + def balance do + css("[data-test='address_balance']") + end + + def token_balance(count: count) do + css("[data-dropdown-token-balance-test]", count: count) + end + + def token_balance_counter(text) do + css("[data-tokens-count]", text: "#{text} tokens") + end + + def token_type(count: count) do + css("[data-token-type]", count: count) + end + + def token_type_count(type: type, text: text) do + css("[data-number-of-tokens-by-type='#{type}']", text: text) + end + + def address(%Address{} = address) do + css("[data-address-hash='#{address}']", text: to_string(address)) + end + + def contract_creator do + css("[data-test='address_contract_creator']") + end + + def click_internal_transactions(session) do + click(session, css("[data-test='internal_transactions_tab_link']")) + end + + def click_tokens(session) do + click(session, css("[data-test='tokens_tab_link']")) + end + + def click_coin_balance_history(session) do + click(session, css("[data-test='coin_balance_tab_link']")) + end + + def click_balance_dropdown_toggle(session) do + click(session, css("[data-dropdown-toggle]")) + end + + def fill_balance_dropdown_search(session, text) do + fill_in(session, css("[data-filter-dropdown-tokens]"), with: text) + end + + def click_outside_of_the_dropdown(session) do + click(session, css("[data-test='outside_of_dropdown']")) + end + + def click_token_transfers(session, %Token{contract_address_hash: contract_address_hash}) do + click(session, css("[data-test='token_transfers_#{contract_address_hash}']")) + end + + def click_show_pending_transactions(session) do + click(session, css("[data-selector='pending-transactions-open']")) + end + + def coin_balances(count: count) do + css("[data-test='coin_balance']", count: count) + end + + def contract_creation(%InternalTransaction{created_contract_address_hash: hash}) do + css("[data-address-hash='#{hash}']", text: to_string(hash)) + end + + def detail_hash(address) do + css("[data-test='address_detail_hash']", text: to_string(address)) + end + + def internal_transaction(%InternalTransaction{transaction_hash: transaction_hash, index: index}) do + css( + "[data-test='internal_transaction']" <> + "[data-internal-transaction-transaction-hash='#{transaction_hash}']" <> + "[data-internal-transaction-index='#{index}']" + ) + end + + def internal_transactions(count: count) do + css("[data-test='internal_transaction']", count: count) + end + + def internal_transaction_address_link( + %InternalTransaction{transaction_hash: transaction_hash, index: index, from_address_hash: address_hash}, + :from + ) do + checksum = Address.checksum(address_hash) + + css( + "[data-internal-transaction-transaction-hash='#{transaction_hash}'][data-internal-transaction-index='#{index}']" <> + " [data-test='address_hash_link']" <> " [data-address-hash='#{checksum}']" + ) + end + + def internal_transaction_address_link( + %InternalTransaction{transaction_hash: transaction_hash, index: index, to_address_hash: address_hash}, + :to + ) do + css( + "[data-internal-transaction-transaction-hash='#{transaction_hash}'][data-internal-transaction-index='#{index}']" <> + " [data-test='address_hash_link']" <> " [data-address-hash='#{address_hash}']" + ) + end + + def pending_transaction(%Transaction{hash: transaction_hash}), do: pending_transaction(transaction_hash) + + def pending_transaction(transaction_hash) do + css("[data-selector='pending-transactions-list'] [data-transaction-hash='#{transaction_hash}']") + end + + def transaction(%Transaction{hash: transaction_hash}), do: transaction(transaction_hash) + + def transaction(%Hash{} = hash) do + hash + |> to_string() + |> transaction() + end + + def transaction(transaction_hash) do + css("[data-identifier-hash='#{transaction_hash}']") + end + + def transaction_address_link(%Transaction{hash: hash, from_address_hash: address_hash}, :from) do + checksum = Address.checksum(address_hash) + + css("[data-identifier-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{checksum}']") + end + + def transaction_address_link(%Transaction{hash: hash, to_address_hash: address_hash}, :to) do + checksum = Address.checksum(address_hash) + + css("[data-identifier-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{checksum}']") + end + + def transaction_status(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='transaction_status']") + end + + def token_transfer(%Transaction{hash: transaction_hash}, %Address{} = address, count: count) do + css( + "[data-identifier-hash='#{transaction_hash}'] [data-test='token_transfer'] [data-address-hash='#{address}']", + count: count + ) + end + + def token_transfers(%Transaction{hash: transaction_hash}, count: count) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='token_transfer']", count: count) + end + + def token_transfers_expansion(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']") + end + + def visit_page(session, %Address{hash: address_hash}), do: visit_page(session, address_hash) + + def visit_page(session, address_hash) do + visit(session, "/address/#{address_hash}") + end + + def visit_page(session) do + visit(session, "/accounts") + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/app_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/app_page.ex new file mode 100644 index 0000000..98b934a --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/app_page.ex @@ -0,0 +1,19 @@ +defmodule BlockScoutWeb.AppPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1, css: 2] + + def visit_page(session) do + visit(session, "/") + end + + def indexed_status(text) do + css("[data-selector='indexed-status'] [data-indexed-ratio]", text: text) + end + + def still_indexing?() do + css("[data-selector='indexed-status']") + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex new file mode 100644 index 0000000..f59cf42 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex @@ -0,0 +1,33 @@ +defmodule BlockScoutWeb.BlockListPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1, css: 2] + + alias Explorer.Chain.Block + + def visit_page(session) do + visit(session, "/blocks") + end + + def visit_reorgs_page(session) do + visit(session, "/reorgs") + end + + def visit_uncles_page(session) do + visit(session, "/uncles") + end + + def block(%Block{number: block_number}) do + css("[data-block-number='#{block_number}']") + end + + def place_holder_blocks(count) do + css("[data-selector='place-holder']", count: count) + end + + def blocks(count) do + css("[data-selector='block-tile']", count: count) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/block_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/block_page.ex new file mode 100644 index 0000000..18289e0 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/block_page.ex @@ -0,0 +1,50 @@ +defmodule BlockScoutWeb.BlockPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1, css: 2] + + alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction} + + def contract_creation(%InternalTransaction{created_contract_address_hash: hash}) do + checksum = Address.checksum(hash) + css("[data-address-hash='#{checksum}']") + end + + def detail_number(%Block{number: block_number}) do + css("[data-test='block_detail_number']", text: to_string(block_number)) + end + + def page_type(type) do + css("[data-test='detail_type']", text: type) + end + + def token_transfers(%Transaction{hash: transaction_hash}, count: count) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='token_transfer']", count: count) + end + + def token_transfers_expansion(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']") + end + + def transaction(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}']") + end + + def transaction_status(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='transaction_status']") + end + + def uncle_link(%Block{hash: hash}) do + css("[data-test='uncle_link'][data-uncle-hash='#{hash}']") + end + + def visit_page(session, %Block{number: block_number, consensus: true}) do + visit(session, "/blocks/#{block_number}") + end + + def visit_page(session, %Block{hash: hash}) do + visit(session, "/blocks/#{hash}") + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex new file mode 100644 index 0000000..7781210 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex @@ -0,0 +1,48 @@ +defmodule BlockScoutWeb.ChainPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1, css: 2] + + alias Explorer.Chain.{Address, Block, Transaction} + + def block(%Block{number: block_number}) do + css("[data-block-number='#{block_number}']") + end + + def blocks(count: count) do + css("[data-selector='chain-block']", count: count) + end + + def contract_creation(%Transaction{created_contract_address_hash: hash}) do + checksum = Address.checksum(hash) + css("[data-test='contract-creation'] [data-address-hash='#{checksum}']") + end + + def place_holder_blocks(count) do + css("[data-selector='place-holder']", count: count) + end + + def search(session, text) do + session + |> fill_in(css("[data-test='search_input']"), with: text) + |> send_keys([:enter]) + end + + def token_transfers(%Transaction{hash: transaction_hash}, count: count) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='token_transfer']", count: count) + end + + def token_transfers_expansion(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']") + end + + def transactions(count: count) do + css("[data-test='chain_transaction']", count: count) + end + + def visit_page(session) do + visit(session, "/") + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex new file mode 100644 index 0000000..f57a723 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex @@ -0,0 +1,58 @@ +defmodule BlockScoutWeb.ContractVerifyPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query + + def visit_page(session, address_hash) do + visit(session, "/address/#{address_hash}/verify-via-flattened-code/new") + end + + def fill_form(session, %{ + contract_name: contract_name, + version: version, + optimization: optimization, + source_code: source_code, + evm_version: evm_version + }) do + session + |> fill_in(css("[data-test='contract_name']"), with: contract_name) + |> fill_in(text_field("Enter the Solidity Contract Code"), with: source_code) + + case version do + nil -> nil + _ -> click(session, option(version)) + end + + case evm_version do + nil -> nil + _ -> click(session, option(evm_version)) + end + + case optimization do + true -> + click(session, radio_button("Yes")) + + false -> + click(session, radio_button("No")) + + _ -> + nil + end + + session + end + + def validation_error do + css("[data-test='contract-source-code-error']") + end + + def has_message?(session, message) do + String.contains?(page_source(session), message) + end + + def verify_and_publish(session) do + click(session, button("Verify & publish")) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/token_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/token_page.ex new file mode 100644 index 0000000..23eb418 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/token_page.ex @@ -0,0 +1,27 @@ +defmodule BlockScoutWeb.TokenPage do + @moduledoc false + + use Wallaby.DSL + import Wallaby.Query, only: [css: 1, css: 2] + alias Explorer.Chain.{Address} + + def visit_page(session, %Address{hash: address_hash}) do + visit_page(session, address_hash) + end + + def visit_page(session, contract_address_hash) do + visit(session, "tokens/#{contract_address_hash}/token-holders") + end + + def token_holders_tab(count: count) do + css("[data-test='token_holders_tab']", count: count) + end + + def click_tokens_holders(session) do + click(session, css("[data-test='token_holders_tab']")) + end + + def token_holders(count: count) do + css("[data-test='token_holders']", count: count) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex new file mode 100644 index 0000000..9b3c34f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex @@ -0,0 +1,33 @@ +defmodule BlockScoutWeb.TransactionListPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1, css: 2] + + alias Explorer.Chain.Transaction + + def click_transaction(session, %Transaction{hash: transaction_hash}) do + click(session, css("[data-identifier-hash='#{transaction_hash}'] [data-test='transaction_hash_link']")) + end + + def contract_creation(%Transaction{hash: hash}) do + css("[data-identifier-hash='#{hash}'] [data-test='transaction_type']", text: "Contract Creation") + end + + def transaction(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}']") + end + + def transaction_status(%Transaction{hash: transaction_hash}) do + css("[data-identifier-hash='#{transaction_hash}'] [data-test='transaction_status']") + end + + def visit_page(session) do + visit(session, "/txs") + end + + def visit_pending_transactions_page(session) do + visit(session, "/pending-transactions") + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_logs_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_logs_page.ex new file mode 100644 index 0000000..3969cd5 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_logs_page.ex @@ -0,0 +1,23 @@ +defmodule BlockScoutWeb.TransactionLogsPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1, css: 2] + import BlockScoutWeb.WebRouter.Helpers, only: [transaction_log_path: 3] + + alias Explorer.Chain.Transaction + alias BlockScoutWeb.Endpoint + + def logs(count: count) do + css("[data-test='transaction_log']", count: count) + end + + def visit_page(session, %Transaction{} = transaction) do + visit(session, transaction_log_path(Endpoint, :index, transaction)) + end + + def click_address(session, address) do + click(session, css("[data-test='log_address_link'][data-address-hash='#{address}']")) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex new file mode 100644 index 0000000..19fe499 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex @@ -0,0 +1,25 @@ +defmodule BlockScoutWeb.TransactionPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1, css: 2] + + alias Explorer.Chain.{Transaction, Hash} + + def click_logs(session) do + click(session, css("[data-test='transaction_logs_link']")) + end + + def detail_hash(%Transaction{hash: transaction_hash}) do + css("[data-test='transaction_detail_hash']", text: Hash.to_string(transaction_hash)) + end + + def is_pending() do + css("[data-selector='block-number']", text: "Pending") + end + + def visit_page(session, %Transaction{hash: transaction_hash}) do + visit(session, "/tx/#{transaction_hash}") + 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 new file mode 100644 index 0000000..6a8fd91 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs @@ -0,0 +1,496 @@ +defmodule BlockScoutWeb.ViewingAddressesTest do + use BlockScoutWeb.FeatureCase, + # Because ETS tables is shared for `Explorer.Counters.*` + async: false + + alias Explorer.Counters.AddressesCounter + alias BlockScoutWeb.{AddressPage, AddressView, Notifier} + + setup do + Application.put_env(:block_scout_web, :checksum_address_hashes, false) + + block = insert(:block, number: 42) + + lincoln = insert(:address, fetched_coin_balance: 5) + taft = insert(:address, fetched_coin_balance: 5) + + from_taft = + :transaction + |> insert(from_address: taft, to_address: lincoln) + |> with_block(block) + + from_lincoln = + :transaction + |> insert(from_address: lincoln, to_address: taft) + |> with_block(block) + + lincoln_reward = + :reward + |> insert( + address_hash: lincoln.hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + taft_reward = + :reward + |> insert( + address_hash: taft.hash, + block_hash: block.hash, + address_type: :validator + ) + + on_exit(fn -> + Application.put_env(:block_scout_web, :checksum_address_hashes, true) + end) + + {:ok, + %{ + addresses: %{lincoln: lincoln, taft: taft}, + block: block, + rewards: {lincoln_reward, taft_reward}, + transactions: %{from_lincoln: from_lincoln, from_taft: from_taft} + }} + end + + describe "viewing top addresses" do + setup do + addresses = Enum.map(150..101, &insert(:address, fetched_coin_balance: &1)) + + {:ok, %{addresses: addresses}} + end + + test "lists top addresses", %{session: session, addresses: addresses} do + [first_address | _] = addresses + [last_address | _] = Enum.reverse(addresses) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> AddressPage.visit_page() + |> assert_has(AddressPage.address(first_address)) + |> assert_has(AddressPage.address(last_address)) + end + end + + test "viewing address overview information", %{session: session} do + address = insert(:address, fetched_coin_balance: 500) + + session + |> AddressPage.visit_page(address) + |> assert_text(AddressPage.balance(), "0.0000000000000005 ETH") + end + + describe "viewing contract creator" do + test "see the contract creator and transaction links", %{session: session} do + address = insert(:address) + contract = insert(:contract_address) + transaction = insert(:transaction, from_address: address, created_contract_address: contract) |> with_block() + + internal_transaction = + insert( + :internal_transaction_create, + index: 1, + transaction: transaction, + from_address: address, + created_contract_address: contract, + block_hash: transaction.block_hash, + block_index: 1 + ) + + address_hash = AddressView.trimmed_hash(address.hash) + transaction_hash = AddressView.trimmed_hash(transaction.hash) + + session + |> AddressPage.visit_page(internal_transaction.created_contract_address) + |> assert_text(AddressPage.contract_creator(), "#{address_hash} at #{transaction_hash}") + end + + test "see the contract creator and transaction links even when the creator is another contract", %{session: session} do + lincoln = insert(:address) + contract = insert(:contract_address) + transaction = insert(:transaction) |> with_block() + another_contract = insert(:contract_address) + + insert( + :internal_transaction, + index: 1, + transaction: transaction, + from_address: lincoln, + to_address: contract, + created_contract_address: contract, + type: :call, + block_hash: transaction.block_hash, + block_index: 1 + ) + + internal_transaction = + insert( + :internal_transaction_create, + index: 2, + transaction: transaction, + from_address: contract, + created_contract_address: another_contract, + block_hash: transaction.block_hash, + block_index: 2 + ) + + contract_hash = AddressView.trimmed_hash(contract.hash) + transaction_hash = AddressView.trimmed_hash(transaction.hash) + + session + |> AddressPage.visit_page(internal_transaction.created_contract_address) + |> assert_text(AddressPage.contract_creator(), "#{contract_hash} at #{transaction_hash}") + end + end + + describe "viewing transactions" do + test "sees all addresses transactions by default", %{ + addresses: addresses, + session: session, + transactions: transactions + } do + session + |> AddressPage.visit_page(addresses.lincoln) + |> assert_has(AddressPage.transaction(transactions.from_taft)) + |> assert_has(AddressPage.transaction(transactions.from_lincoln)) + |> assert_has(AddressPage.transaction_status(transactions.from_lincoln)) + end + + test "can filter to only see transactions from an address", %{ + addresses: addresses, + session: session, + transactions: transactions + } do + session + |> AddressPage.visit_page(addresses.lincoln) + |> AddressPage.apply_filter("From") + |> assert_has(AddressPage.transaction(transactions.from_lincoln)) + |> refute_has(AddressPage.transaction(transactions.from_taft)) + end + + test "can filter to only see transactions to an address", %{ + addresses: addresses, + session: session, + transactions: transactions + } do + session + |> AddressPage.visit_page(addresses.lincoln) + |> AddressPage.apply_filter("To") + |> refute_has(AddressPage.transaction(transactions.from_lincoln)) + |> assert_has(AddressPage.transaction(transactions.from_taft)) + end + + test "only addresses not matching the page are links", %{ + addresses: addresses, + session: session, + transactions: transactions + } do + session + |> AddressPage.visit_page(addresses.lincoln) + |> assert_has(AddressPage.transaction_address_link(transactions.from_lincoln, :to)) + |> refute_has(AddressPage.transaction_address_link(transactions.from_lincoln, :from)) + end + + test "sees rewards to and from an address alongside transactions", %{ + addresses: addresses, + session: session, + transactions: transactions + } do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + session + |> AddressPage.visit_page(addresses.lincoln) + |> assert_has(AddressPage.transaction(transactions.from_taft)) + |> assert_has(AddressPage.transaction(transactions.from_lincoln)) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + end + + describe "viewing internal transactions" do + setup %{addresses: addresses, transactions: transactions} do + address = addresses.lincoln + transaction = transactions.from_lincoln + + internal_transaction_lincoln_to_address = + insert(:internal_transaction, + transaction: transaction, + to_address: address, + index: 1, + block_number: 7000, + transaction_index: 1, + block_hash: transaction.block_hash, + block_index: 1 + ) + + insert(:internal_transaction, + transaction: transaction, + from_address: address, + index: 2, + block_number: 8000, + transaction_index: 2, + block_hash: transaction.block_hash, + block_index: 2 + ) + + {:ok, %{internal_transaction_lincoln_to_address: internal_transaction_lincoln_to_address}} + end + + test "only addresses not matching the page are links", %{ + addresses: addresses, + internal_transaction_lincoln_to_address: internal_transaction, + session: session + } do + session + |> AddressPage.visit_page(addresses.lincoln) + |> AddressPage.click_internal_transactions() + |> assert_has(AddressPage.internal_transaction_address_link(internal_transaction, :from)) + |> refute_has(AddressPage.internal_transaction_address_link(internal_transaction, :to)) + end + + test "viewing new internal transactions via live update", %{addresses: addresses, session: session} do + transaction = + :transaction + |> insert(from_address: addresses.lincoln) + |> with_block(insert(:block, number: 7000)) + + session + |> AddressPage.visit_page(addresses.lincoln) + |> AddressPage.click_internal_transactions() + |> assert_has(AddressPage.internal_transactions(count: 2)) + + internal_transaction = + insert(:internal_transaction, + transaction: transaction, + index: 2, + from_address: addresses.lincoln, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 2 + ) + + Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) + + session + |> assert_has(AddressPage.internal_transactions(count: 3)) + |> assert_has(AddressPage.internal_transaction(internal_transaction)) + end + + test "can filter to see internal transactions from an address only", %{ + addresses: addresses, + session: session + } do + block = insert(:block, number: 7000) + + from_lincoln = + :transaction + |> insert(from_address: addresses.lincoln) + |> with_block(block) + + from_taft = + :transaction + |> insert(from_address: addresses.taft) + |> with_block(block) + + insert(:internal_transaction, + transaction: from_lincoln, + index: 2, + from_address: addresses.lincoln, + block_number: from_lincoln.block_number, + transaction_index: from_lincoln.index, + block_hash: from_lincoln.block_hash, + block_index: 2 + ) + + session + |> AddressPage.visit_page(addresses.lincoln) + |> AddressPage.apply_filter("From") + |> assert_has(AddressPage.transaction(from_lincoln)) + |> refute_has(AddressPage.transaction(from_taft)) + end + + test "can filter to see internal transactions to an address only", %{ + addresses: addresses, + session: session + } do + block = insert(:block, number: 7000) + + from_lincoln = + :transaction + |> insert(to_address: addresses.lincoln) + |> with_block(block) + + from_taft = + :transaction + |> insert(to_address: addresses.taft) + |> with_block(block) + + insert(:internal_transaction, + transaction: from_lincoln, + index: 2, + from_address: addresses.lincoln, + block_number: from_lincoln.block_number, + transaction_index: from_lincoln.index, + block_hash: from_lincoln.block_hash, + block_index: 2 + ) + + session + |> AddressPage.visit_page(addresses.lincoln) + |> AddressPage.apply_filter("To") + |> assert_has(AddressPage.transaction(from_lincoln)) + |> refute_has(AddressPage.transaction(from_taft)) + end + end + + describe "viewing token transfers from a specific token" do + test "list token transfers related to the address", %{ + addresses: addresses, + block: block, + session: session + } do + lincoln = addresses.lincoln + taft = addresses.taft + + contract_address = insert(:contract_address) + token = insert(:token, contract_address: contract_address) + + transaction = + :transaction + |> insert(from_address: lincoln, to_address: contract_address) + |> with_block(block) + + insert( + :token_transfer, + from_address: lincoln, + to_address: taft, + transaction: transaction, + token_contract_address: contract_address + ) + + insert(:address_current_token_balance, address: lincoln, token_contract_address_hash: contract_address.hash) + + session + |> AddressPage.visit_page(lincoln) + |> AddressPage.click_tokens() + |> AddressPage.click_token_transfers(token) + |> assert_has(AddressPage.token_transfers(transaction, count: 1)) + |> assert_has(AddressPage.token_transfer(transaction, lincoln, count: 1)) + |> assert_has(AddressPage.token_transfer(transaction, taft, count: 1)) + |> refute_has(AddressPage.token_transfers_expansion(transaction)) + end + end + + describe "viewing token balances" do + setup do + block = insert(:block) + lincoln = insert(:address, fetched_coin_balance: 5, fetched_coin_balance_block_number: block.number) + taft = insert(:address, fetched_coin_balance: 5) + + contract_address = insert(:contract_address) + insert(:token, name: "atoken", symbol: "AT", contract_address: contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert(from_address: lincoln, to_address: contract_address) + |> with_block(block) + + insert( + :token_transfer, + from_address: lincoln, + to_address: taft, + transaction: transaction, + token_contract_address: contract_address + ) + + insert(:address_current_token_balance, address: lincoln, token_contract_address_hash: contract_address.hash) + + contract_address_2 = insert(:contract_address) + insert(:token, name: "token2", symbol: "T2", contract_address: contract_address_2, type: "ERC-20") + + transaction_2 = + :transaction + |> insert(from_address: lincoln, to_address: contract_address_2) + |> with_block(block) + + insert( + :token_transfer, + from_address: lincoln, + to_address: taft, + transaction: transaction_2, + token_contract_address: contract_address_2 + ) + + insert(:address_current_token_balance, address: lincoln, token_contract_address_hash: contract_address_2.hash) + + {:ok, lincoln: lincoln} + end + + test "filter tokens balances by token name", %{session: session, lincoln: lincoln} do + next = + session + |> AddressPage.visit_page(lincoln) + + Process.sleep(2_000) + + next + |> AddressPage.click_balance_dropdown_toggle() + |> AddressPage.fill_balance_dropdown_search("ato") + |> assert_has(AddressPage.token_balance(count: 1)) + |> assert_has(AddressPage.token_type(count: 1)) + |> assert_has(AddressPage.token_type_count(type: "ERC-721", text: "1")) + end + + test "filter token balances by token symbol", %{session: session, lincoln: lincoln} do + next = + session + |> AddressPage.visit_page(lincoln) + + Process.sleep(2_000) + + next + |> AddressPage.click_balance_dropdown_toggle() + |> AddressPage.fill_balance_dropdown_search("T2") + |> assert_has(AddressPage.token_balance(count: 1)) + |> assert_has(AddressPage.token_type(count: 1)) + |> assert_has(AddressPage.token_type_count(type: "ERC-20", text: "1")) + end + + test "reset token balances filter when dropdown closes", %{session: session, lincoln: lincoln} do + next = + session + |> AddressPage.visit_page(lincoln) + + Process.sleep(2_000) + + next + |> AddressPage.click_balance_dropdown_toggle() + |> AddressPage.fill_balance_dropdown_search("ato") + |> AddressPage.click_outside_of_the_dropdown() + |> assert_has(AddressPage.token_balance_counter("2")) + end + end + + describe "viewing coin balance history" do + setup do + address = insert(:address, fetched_coin_balance: 5) + noon = Timex.now() |> Timex.beginning_of_day() |> Timex.set(hour: 12) + block = insert(:block, timestamp: noon) + block_one_day_ago = insert(:block, timestamp: Timex.shift(noon, days: -1)) + insert(:fetched_balance, address_hash: address.hash, value: 5, block_number: block.number) + insert(:fetched_balance, address_hash: address.hash, value: 10, block_number: block_one_day_ago.number) + + {:ok, address: address} + end + + test "see list of coin balances", %{session: session, address: address} do + session + |> AddressPage.visit_page(address) + |> AddressPage.click_coin_balance_history() + |> assert_has(AddressPage.coin_balances(count: 2)) + end + end +end 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 new file mode 100644 index 0000000..ad91898 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs @@ -0,0 +1,144 @@ +defmodule BlockScoutWeb.ViewingAppTest do + @moduledoc false + + # use BlockScoutWeb.FeatureCase, async: true + + # alias BlockScoutWeb.AppPage + # alias BlockScoutWeb.Counters.BlocksIndexedCounter + # alias Explorer.Counters.AddressesCounter + # alias Explorer.{Repo} + # alias Explorer.Chain.PendingBlockOperation + + # setup do + # start_supervised!(AddressesCounter) + # AddressesCounter.consolidate() + + # :ok + # end + + # describe "loading bar when indexing" do + # test "shows blocks indexed percentage", %{session: session} do + # [block | _] = + # for index <- 5..9 do + # insert(:block, number: index) + # end + + # :transaction + # |> insert() + # |> with_block(block) + + # 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) + + # session + # |> AppPage.visit_page() + # |> assert_has(AppPage.indexed_status("50% Blocks Indexed")) + # end + + # test "shows tokens loading", %{session: session} do + # [block | _] = + # for index <- 0..9 do + # insert(:block, number: index) + # end + + # :transaction + # |> insert() + # |> with_block(block) + + # assert Decimal.compare(Explorer.Chain.indexed_ratio_blocks(), 1) == :eq + + # insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true) + + # session + # |> AppPage.visit_page() + # |> assert_has(AppPage.indexed_status("Indexing Internal Transactions")) + # end + + # test "updates blocks indexed percentage", %{session: session} do + # [block | _] = + # for index <- 5..9 do + # insert(:block, number: index) + # end + + # :transaction + # |> insert() + # |> with_block(block) + + # BlocksIndexedCounter.calculate_blocks_indexed() + + # 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) + + # session + # |> AppPage.visit_page() + # |> assert_has(AppPage.indexed_status("50% Blocks Indexed")) + + # insert(:block, number: 4) + + # BlocksIndexedCounter.calculate_blocks_indexed() + + # assert_has(session, AppPage.indexed_status("60% Blocks Indexed")) + # end + + # test "updates when blocks are fully indexed", %{session: session} do + # [block | _] = + # for index <- 1..9 do + # insert(:block, number: index) + # end + + # :transaction + # |> insert() + # |> with_block(block) + + # BlocksIndexedCounter.calculate_blocks_indexed() + + # 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) + + # session + # |> AppPage.visit_page() + # |> assert_has(AppPage.indexed_status("90% Blocks Indexed")) + + # insert(:block, number: 0) + + # BlocksIndexedCounter.calculate_blocks_indexed() + + # assert_has(session, AppPage.indexed_status("Indexing Internal Transactions")) + # end + + # test "removes message when chain is indexed", %{session: session} do + # [block | _] = + # for index <- 0..9 do + # insert(:block, number: index) + # end + + # :transaction + # |> insert() + # |> with_block(block) + + # block_hash = block.hash + + # insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true) + + # BlocksIndexedCounter.calculate_blocks_indexed() + + # assert Decimal.compare(Explorer.Chain.indexed_ratio_blocks(), 1) == :eq + + # session + # |> AppPage.visit_page() + # |> assert_has(AppPage.indexed_status("Indexing Internal Transactions")) + + # Repo.update_all( + # from(p in PendingBlockOperation, where: p.block_hash == ^block_hash), + # set: [fetch_internal_transactions: false] + # ) + + # BlocksIndexedCounter.calculate_blocks_indexed() + + # refute_has(session, AppPage.still_indexing?()) + # end + # end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs new file mode 100644 index 0000000..c1f6688 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs @@ -0,0 +1,185 @@ +defmodule BlockScoutWeb.ViewingBlocksTest do + use BlockScoutWeb.FeatureCase, async: false + + alias BlockScoutWeb.{BlockListPage, BlockPage} + alias Explorer.Chain.Block + + setup do + timestamp = Timex.now() |> Timex.shift(hours: -1) + [oldest_block | _] = Enum.map(308..310, &insert(:block, number: &1, timestamp: timestamp, gas_used: 10)) + + newest_block = + insert(:block, %{ + gas_limit: 5_030_101, + gas_used: 1_010_101, + nonce: 123_456_789, + number: 311, + size: 9_999_999, + timestamp: timestamp + }) + + {:ok, first_shown_block: newest_block, last_shown_block: oldest_block} + end + + describe "block details page" do + test "show block detail page", %{session: session} do + block = insert(:block, number: 42) + + session + |> BlockPage.visit_page(block) + |> assert_has(BlockPage.detail_number(block)) + |> assert_has(BlockPage.page_type("Block Details")) + end + + test "block detail page has transactions", %{session: session} do + block = insert(:block, number: 42) + + transaction = + :transaction + |> insert() + |> with_block(block) + + session + |> BlockPage.visit_page(block) + |> assert_has(BlockPage.detail_number(block)) + |> assert_has(BlockPage.transaction(transaction)) + |> assert_has(BlockPage.transaction_status(transaction)) + end + + test "contract creation is shown for to_address in transaction list", %{session: session} do + block = insert(:block, number: 42) + + contract_address = insert(:contract_address) + + transaction = + :transaction + |> insert(to_address: nil) + |> with_contract_creation(contract_address) + |> with_block(block) + + internal_transaction = + :internal_transaction_create + |> insert( + transaction: transaction, + index: 0, + block_hash: transaction.block_hash, + block_index: 1 + ) + |> with_contract_creation(contract_address) + + session + |> BlockPage.visit_page(block) + |> assert_has(BlockPage.contract_creation(internal_transaction)) + end + + test "transaction with multiple token transfers shows all transfers if expanded", %{ + first_shown_block: block, + session: session + } do + contract_token_address = insert(:contract_address) + insert(:token, contract_address: contract_token_address) + + transaction = + :transaction + |> insert(to_address: contract_token_address) + |> with_block(block) + + insert_list( + 3, + :token_transfer, + transaction: transaction, + token_contract_address: contract_token_address, + block: block + ) + + session + |> BlockPage.visit_page(block) + |> assert_has(BlockPage.token_transfers(transaction, count: 1)) + |> click(BlockPage.token_transfers_expansion(transaction)) + |> assert_has(BlockPage.token_transfers(transaction, count: 3)) + end + + test "show reorg detail page", %{session: session} do + reorg = insert(:block, consensus: false) + + session + |> BlockPage.visit_page(reorg) + |> assert_has(BlockPage.detail_number(reorg)) + |> assert_has(BlockPage.page_type("Reorg Details")) + end + + test "show uncle detail page", %{session: session} do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash) + + session + |> BlockPage.visit_page(uncle) + |> assert_has(BlockPage.detail_number(uncle)) + |> assert_has(BlockPage.page_type("Uncle Details")) + end + + test "show link to uncle on block detail page", %{session: session} do + block = insert(:block) + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: block) + + session + |> BlockPage.visit_page(block) + |> assert_has(BlockPage.detail_number(block)) + |> assert_has(BlockPage.page_type("Block Details")) + |> assert_has(BlockPage.uncle_link(uncle)) + end + end + + describe "viewing blocks list" do + test "viewing the blocks index page", %{first_shown_block: block, session: session} do + session + |> BlockListPage.visit_page() + |> assert_has(BlockListPage.block(block)) + end + + test "inserts place holder blocks on render for out of order blocks", %{session: session} do + insert(:block, number: 315) + + session + |> BlockListPage.visit_page() + |> assert_has(BlockListPage.block(%Block{number: 314})) + |> assert_has(BlockListPage.place_holder_blocks(3)) + end + end + + describe "viewing uncle blocks list" do + setup do + uncles = + for _index <- 1..10 do + uncle = insert(:block, consensus: false) + insert(:block_second_degree_relation, uncle_hash: uncle.hash) + + transaction = insert(:transaction) + insert(:transaction_fork, hash: transaction.hash, uncle_hash: uncle.hash) + + uncle + end + + {:ok, %{uncles: uncles}} + end + + test "lists uncle blocks", %{session: session, uncles: [uncle | _]} do + session + |> BlockListPage.visit_uncles_page() + |> assert_has(BlockListPage.block(uncle)) + |> assert_has(BlockListPage.blocks(10)) + end + end + + describe "viewing reorg blocks list" do + test "lists uncle blocks", %{session: session} do + [reorg | _] = insert_list(10, :block, consensus: false) + + session + |> BlockListPage.visit_reorgs_page() + |> assert_has(BlockListPage.block(reorg)) + |> assert_has(BlockListPage.blocks(10)) + end + end +end 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 new file mode 100644 index 0000000..5216623 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs @@ -0,0 +1,160 @@ +defmodule BlockScoutWeb.ViewingChainTest do + @moduledoc false + + use BlockScoutWeb.FeatureCase, + # MUST Be false because ETS tables for Counters are shared + async: false + + alias BlockScoutWeb.{AddressPage, BlockPage, ChainPage, TransactionPage} + alias Explorer.Chain.Block + alias Explorer.Counters.AddressesCounter + + setup do + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + + Enum.map(401..404, &insert(:block, number: &1)) + + block = insert(:block, number: 405) + + 4 + |> insert_list(:transaction) + |> with_block(block) + + :transaction + |> insert() + |> with_block(block) + + {:ok, + %{ + block: block + }} + end + + describe "viewing addresses" do + test "search for address", %{session: session} do + address = insert(:address) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> ChainPage.visit_page() + |> ChainPage.search(to_string(address.hash)) + |> assert_has(AddressPage.detail_hash(address)) + end + end + + describe "viewing blocks" do + test "search for blocks from chain page", %{session: session} do + block = insert(:block, number: 6) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> ChainPage.visit_page() + |> ChainPage.search(to_string(block.number)) + |> assert_has(BlockPage.detail_number(block)) + end + + test "blocks list", %{session: session} do + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> ChainPage.visit_page() + |> assert_has(ChainPage.blocks(count: 4)) + end + + test "inserts place holder blocks on render for out of order blocks", %{session: session} do + insert(:block, number: 409) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> ChainPage.visit_page() + |> assert_has(ChainPage.block(%Block{number: 408})) + |> assert_has(ChainPage.place_holder_blocks(3)) + end + end + + describe "viewing transactions" do + test "search for transactions", %{session: session} do + block = insert(:block) + + transaction = + insert(:transaction) + |> with_block(block) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> ChainPage.visit_page() + |> ChainPage.search(to_string(transaction.hash)) + |> assert_has(TransactionPage.detail_hash(transaction)) + end + + test "transactions list", %{session: session} do + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> ChainPage.visit_page() + |> assert_has(ChainPage.transactions(count: 5)) + end + + test "contract creation is shown for to_address", %{session: session, block: block} do + contract_address = insert(:contract_address) + + transaction = + :transaction + |> insert(to_address: nil) + |> with_contract_creation(contract_address) + |> with_block(block) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + session + |> ChainPage.visit_page() + |> assert_has(ChainPage.contract_creation(transaction)) + end + + test "transaction with multiple token transfers shows all transfers if expanded", %{ + block: block, + session: session + } do + contract_token_address = insert(:contract_address) + insert(:token, contract_address: contract_token_address) + + transaction = + :transaction + |> insert(to_address: contract_token_address) + |> with_block(block, status: :ok) + + insert_list( + 3, + :token_transfer, + transaction: transaction, + token_contract_address: contract_token_address, + block: block + ) + + start_supervised!(AddressesCounter) + AddressesCounter.consolidate() + + ChainPage.visit_page(session) + + # wait for the `transactions-list` to load + :timer.sleep(1000) + + session + |> assert_has(ChainPage.token_transfers(transaction, count: 1)) + |> click(ChainPage.token_transfers_expansion(transaction)) + |> assert_has(ChainPage.token_transfers(transaction, count: 3)) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_tokens_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_tokens_test.exs new file mode 100644 index 0000000..d896266 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_tokens_test.exs @@ -0,0 +1,21 @@ +defmodule BlockScoutWeb.ViewingTokensTest do + use BlockScoutWeb.FeatureCase, async: true + + alias BlockScoutWeb.TokenPage + + describe "viewing token holders" do + test "list the token holders", %{session: session} do + token = insert(:token) + + insert_list( + 2, + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash + ) + + session + |> TokenPage.visit_page(token.contract_address) + |> assert_has(TokenPage.token_holders(count: 2)) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs new file mode 100644 index 0000000..33c8fe9 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs @@ -0,0 +1,163 @@ +defmodule BlockScoutWeb.ViewingTransactionsTest do + @moduledoc false + + import Mox + + use BlockScoutWeb.FeatureCase, async: false + + alias BlockScoutWeb.{AddressPage, TransactionListPage, TransactionLogsPage, TransactionPage} + alias Explorer.Chain.Wei + + setup :set_mox_global + + setup do + block = + insert(:block, %{ + timestamp: Timex.now() |> Timex.shift(hours: -2), + gas_used: 123_987 + }) + + 3 + |> insert_list(:transaction) + |> with_block() + + pending = insert(:transaction, block_hash: nil, gas: 5891, index: nil) + pending_contract = insert(:transaction, to_address: nil, block_hash: nil, gas: 5891, index: nil) + + lincoln = insert(:address) + taft = insert(:address) + + # From Lincoln to Taft. + txn_from_lincoln = + :transaction + |> insert(from_address: lincoln, to_address: taft) + |> with_block(block) + + transaction = + :transaction + |> insert( + value: Wei.from(Decimal.new(5656), :ether), + gas: Decimal.new(1_230_000_000_000_123_123), + gas_price: Decimal.new(7_890_000_000_898_912_300_045), + input: "0x000012", + nonce: 99045, + inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"), + updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"), + from_address: taft, + to_address: lincoln + ) + |> with_block(block, gas_used: Decimal.new(1_230_000_000_000_123_000), status: :ok) + + insert(:log, address: lincoln, index: 0, transaction: transaction, block: block, block_number: block.number) + + internal = + insert(:internal_transaction, + index: 0, + transaction: transaction, + block_hash: transaction.block_hash, + block_index: 0 + ) + + {:ok, + %{ + pending: pending, + pending_contract: pending_contract, + internal: internal, + lincoln: lincoln, + taft: taft, + transaction: transaction, + txn_from_lincoln: txn_from_lincoln + }} + end + + describe "viewing transaction lists" do + test "viewing the default transactions tab", %{ + session: session, + transaction: transaction, + pending: pending + } do + session + |> TransactionListPage.visit_page() + |> assert_has(TransactionListPage.transaction(transaction)) + |> assert_has(TransactionListPage.transaction_status(transaction)) + |> refute_has(TransactionListPage.transaction(pending)) + end + + test "viewing the pending tranasctions list", %{ + pending: pending, + pending_contract: pending_contract, + session: session + } do + session + |> TransactionListPage.visit_pending_transactions_page() + |> assert_has(TransactionListPage.transaction(pending)) + |> assert_has(TransactionListPage.transaction(pending_contract)) + |> assert_has(TransactionListPage.transaction_status(pending_contract)) + end + + test "contract creation is shown for to_address on list page", %{session: session} do + contract_address = insert(:contract_address) + + transaction = + :transaction + |> insert(to_address: nil) + |> with_block() + |> with_contract_creation(contract_address) + + :internal_transaction_create + |> insert(transaction: transaction, index: 0, block_hash: transaction.block_hash, block_index: 0) + |> with_contract_creation(contract_address) + + session + |> TransactionListPage.visit_page() + |> assert_has(TransactionListPage.contract_creation(transaction)) + end + end + + describe "viewing a pending transaction page" do + test "can see a pending transaction's details", %{session: session, pending: pending} do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> + {:ok, "100"} + end) + + session + |> TransactionPage.visit_page(pending) + |> assert_has(TransactionPage.detail_hash(pending)) + |> assert_has(TransactionPage.is_pending()) + end + end + + describe "viewing a transaction page" do + test "can navigate to transaction show from list page", %{session: session, transaction: transaction} do + session + |> TransactionListPage.visit_page() + |> TransactionListPage.click_transaction(transaction) + |> assert_has(TransactionPage.detail_hash(transaction)) + end + + test "can see a transaction's details", %{session: session, transaction: transaction} do + session + |> TransactionPage.visit_page(transaction) + |> assert_has(TransactionPage.detail_hash(transaction)) + end + + test "can view a transaction's logs", %{session: session, transaction: transaction} do + session + |> TransactionPage.visit_page(transaction) + |> TransactionPage.click_logs() + |> assert_has(TransactionLogsPage.logs(count: 1)) + end + + test "can visit an address from the transaction logs page", %{ + lincoln: lincoln, + session: session, + transaction: transaction + } do + session + |> TransactionLogsPage.visit_page(transaction) + |> TransactionLogsPage.click_address(lincoln) + |> assert_has(AddressPage.detail_hash(lincoln)) + end + end +end 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 0000000..2d13f11 --- /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/plug/admin/check_owner_registered_test.exs b/apps/block_scout_web/test/block_scout_web/plug/admin/check_owner_registered_test.exs new file mode 100644 index 0000000..2eb7b98 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/plug/admin/check_owner_registered_test.exs @@ -0,0 +1,27 @@ +defmodule BlockScoutWeb.Plug.Admin.CheckOwnerRegisteredTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.Plug.Admin.CheckOwnerRegistered + alias Explorer.Admin + + test "init/1" do + assert CheckOwnerRegistered.init([]) == [] + end + + describe "call/2" do + test "redirects if owner user isn't configured", %{conn: conn} do + assert {:error, _} = Admin.owner() + result = CheckOwnerRegistered.call(conn, []) + assert redirected_to(result) == AdminRoutes.setup_path(conn, :configure) + assert result.halted + end + + test "continues if owner user is configured", %{conn: conn} do + insert(:administrator) + assert {:ok, _} = Admin.owner() + result = CheckOwnerRegistered.call(conn, []) + assert result.state == :unset + refute result.halted + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/plug/admin/require_admin_role_test.exs b/apps/block_scout_web/test/block_scout_web/plug/admin/require_admin_role_test.exs new file mode 100644 index 0000000..45bcdc2 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/plug/admin/require_admin_role_test.exs @@ -0,0 +1,42 @@ +defmodule BlockScoutWeb.Plug.Admin.RequireAdminRoleTest do + use BlockScoutWeb.ConnCase + + import Plug.Conn, only: [put_session: 3, assign: 3] + + alias BlockScoutWeb.Router + alias BlockScoutWeb.Plug.Admin.RequireAdminRole + + test "init/1" do + assert RequireAdminRole.init([]) == [] + end + + describe "call/2" do + setup %{conn: conn} do + conn = + conn + |> bypass_through(Router, [:browser]) + |> get("/") + + {:ok, conn: conn} + end + + test "redirects if user in conn isn't an admin", %{conn: conn} do + result = RequireAdminRole.call(conn, []) + assert redirected_to(result) == AdminRoutes.session_path(conn, :new) + assert result.halted + end + + test "continues if user in assigns is an admin", %{conn: conn} do + administrator = insert(:administrator) + + result = + conn + |> put_session(:user_id, administrator.user.id) + |> assign(:user, administrator.user) + |> RequireAdminRole.call([]) + + refute result.halted + assert result.state == :unset + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/plug/fetch_user_from_session_test.exs b/apps/block_scout_web/test/block_scout_web/plug/fetch_user_from_session_test.exs new file mode 100644 index 0000000..ed00ce6 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/plug/fetch_user_from_session_test.exs @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.Plug.FetchUserFromSessionTest do + use BlockScoutWeb.ConnCase + + import Plug.Conn, only: [put_session: 3] + + alias BlockScoutWeb.Plug.FetchUserFromSession + alias BlockScoutWeb.Router + alias Explorer.Accounts.User + + test "init/1" do + assert FetchUserFromSession.init([]) == [] + end + + describe "call/2" do + setup %{conn: conn} do + conn = + conn + |> bypass_through(Router, [:browser]) + |> get("/") + + {:ok, conn: conn} + end + + test "loads user if valid user id in session", %{conn: conn} do + user = insert(:user) + + result = + conn + |> put_session(:user_id, user.id) + |> FetchUserFromSession.call([]) + + assert %User{} = result.assigns.user + end + + test "returns conn if user id is invalid in session", %{conn: conn} do + conn = put_session(conn, :user_id, 1) + result = FetchUserFromSession.call(conn, []) + + assert conn == result + end + + test "returns conn if no user id is in session", %{conn: conn} do + assert FetchUserFromSession.call(conn, []) == conn + 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 new file mode 100644 index 0000000..134d1b7 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs @@ -0,0 +1,594 @@ +defmodule BlockScoutWeb.Schema.Query.AddressTest do + use BlockScoutWeb.ConnCase + + describe "address field" do + test "with valid argument 'hash', returns all expected fields", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 100) + + query = """ + query ($hash: AddressHash!) { + address(hash: $hash) { + hash + fetched_coin_balance + fetched_coin_balance_block_number + contract_code + } + } + """ + + variables = %{"hash" => to_string(address.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "address" => %{ + "hash" => to_string(address.hash), + "fetched_coin_balance" => to_string(address.fetched_coin_balance.value), + "fetched_coin_balance_block_number" => address.fetched_coin_balance_block_number, + "contract_code" => nil + } + } + } + end + + test "with contract address, `contract_code` is serialized as expected", %{conn: conn} do + address = insert(:contract_address, fetched_coin_balance: 100) + + query = """ + query ($hash: AddressHash!) { + address(hash: $hash) { + contract_code + } + } + """ + + variables = %{"hash" => to_string(address.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "address" => %{ + "contract_code" => to_string(address.contract_code) + } + } + } + end + + test "smart_contract returns all expected fields", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 100) + smart_contract = insert(:smart_contract, address_hash: address.hash, contract_code_md5: "123") + + query = """ + query ($hash: AddressHash!) { + address(hash: $hash) { + fetched_coin_balance + smart_contract { + name + compiler_version + optimization + contract_source_code + abi + address_hash + } + } + } + """ + + variables = %{"hash" => to_string(address.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "address" => %{ + "fetched_coin_balance" => to_string(address.fetched_coin_balance.value), + "smart_contract" => %{ + "name" => smart_contract.name, + "compiler_version" => smart_contract.compiler_version, + "optimization" => smart_contract.optimization, + "contract_source_code" => smart_contract.contract_source_code, + "abi" => Jason.encode!(smart_contract.abi), + "address_hash" => to_string(address.hash) + } + } + } + } + end + + test "errors for non-existent address hash", %{conn: conn} do + address = build(:address) + + query = """ + query ($hash: AddressHash!) { + address(hash: $hash) { + fetched_coin_balance + } + } + """ + + variables = %{"hash" => to_string(address.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] =~ ~s(Address not found.) + end + + test "errors if argument 'hash' is missing", %{conn: conn} do + query = """ + query { + address { + fetched_coin_balance + } + } + """ + + variables = %{} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] == ~s(In argument "hash": Expected type "AddressHash!", found null.) + end + + test "errors if argument 'hash' is not a valid address hash", %{conn: conn} do + query = """ + query ($hash: AddressHash!) { + address(hash: $hash) { + fetched_coin_balance + } + } + """ + + variables = %{"hash" => "someInvalidHash"} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] =~ ~s(Argument "hash" has invalid value) + end + end + + describe "address transactions field" do + test "returns all expected transaction fields", %{conn: conn} do + address = insert(:address) + + transaction = insert(:transaction, from_address: address) + + query = """ + query ($hash: AddressHash!, $first: Int!) { + address(hash: $hash) { + transactions(first: $first) { + edges { + node { + hash + block_number + cumulative_gas_used + error + gas + gas_price + gas_used + index + input + nonce + r + s + status + v + value + from_address_hash + to_address_hash + created_contract_address_hash + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(address.hash), + "first" => 1 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "address" => %{ + "transactions" => %{ + "edges" => [ + %{ + "node" => %{ + "hash" => to_string(transaction.hash), + "block_number" => transaction.block_number, + "cumulative_gas_used" => nil, + "error" => transaction.error, + "gas" => to_string(transaction.gas), + "gas_price" => to_string(transaction.gas_price.value), + "gas_used" => nil, + "index" => transaction.index, + "input" => to_string(transaction.input), + "nonce" => to_string(transaction.nonce), + "r" => to_string(transaction.r), + "s" => to_string(transaction.s), + "status" => nil, + "v" => to_string(transaction.v), + "value" => to_string(transaction.value.value), + "from_address_hash" => to_string(transaction.from_address_hash), + "to_address_hash" => to_string(transaction.to_address_hash), + "created_contract_address_hash" => nil + } + } + ] + } + } + } + } + end + + test "with address with zero transactions", %{conn: conn} do + address = insert(:address) + + query = """ + query ($hash: AddressHash!, $first: Int!) { + address(hash: $hash) { + transactions(first: $first) { + edges { + node { + hash + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(address.hash), + "first" => 1 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "address" => %{ + "transactions" => %{ + "edges" => [] + } + } + } + } + end + + test "transactions are ordered by descending 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) { + 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"] == 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) + + query = """ + query ($hash: AddressHash!, $first: Int!) { + address(hash: $hash) { + transactions(first: $first) { + edges { + node { + hash + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(address.hash), + "first" => 67 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error1, error2, error3]} = json_response(conn, 200) + assert error1["message"] =~ ~s(Field transactions is too complex) + assert error2["message"] =~ ~s(Field address is too complex) + assert error3["message"] =~ ~s(Operation is too complex) + end + + test "with 'last' and 'count' arguments", %{conn: conn} do + # "`last: N` must always be acompanied by either a `before:` argument to + # the query, or an explicit `count:` option to the `from_query` call. + # Otherwise it is impossible to derive the required offset." + # https://hexdocs.pm/absinthe_relay/Absinthe.Relay.Connection.html#from_query/4 + # + # This test ensures support of a 'count' argument. + + 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!, $last: Int!, $count: Int!) { + address(hash: $hash) { + transactions(last: $last, count: $count) { + edges { + node { + hash + block_number + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(address.hash), + "last" => 3, + "count" => 9 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + %{ + "data" => %{ + "address" => %{ + "transactions" => %{ + "edges" => transactions + } + } + } + } = json_response(conn, 200) + + assert length(transactions) == 3 + assert Enum.all?(transactions, &(&1["node"]["block_number"] == first_block.number)) + end + + test "pagination support with 'first' and 'after' arguments", %{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) + + query1 = """ + query ($hash: AddressHash!, $first: Int!) { + address(hash: $hash) { + transactions(first: $first) { + page_info { + has_next_page + has_previous_page + } + edges { + node { + hash + block_number + } + cursor + } + } + } + } + """ + + variables1 = %{ + "hash" => to_string(address.hash), + "first" => 3 + } + + conn = post(conn, "/graphql", query: query1, variables: variables1) + + %{"data" => %{"address" => %{"transactions" => page1}}} = json_response(conn, 200) + + assert page1["page_info"] == %{"has_next_page" => true, "has_previous_page" => false} + assert Enum.all?(page1["edges"], &(&1["node"]["block_number"] == third_block.number)) + + last_cursor_page1 = + page1 + |> Map.get("edges") + |> List.last() + |> Map.get("cursor") + + query2 = """ + query ($hash: AddressHash!, $first: Int!, $after: String!) { + address(hash: $hash) { + transactions(first: $first, after: $after) { + page_info { + has_next_page + has_previous_page + } + edges { + node { + hash + block_number + } + cursor + } + } + } + } + """ + + variables2 = %{ + "hash" => to_string(address.hash), + "first" => 3, + "after" => last_cursor_page1 + } + + conn = post(conn, "/graphql", query: query2, variables: variables2) + + %{"data" => %{"address" => %{"transactions" => page2}}} = json_response(conn, 200) + + assert page2["page_info"] == %{"has_next_page" => true, "has_previous_page" => true} + assert Enum.all?(page2["edges"], &(&1["node"]["block_number"] == second_block.number)) + + last_cursor_page2 = + page2 + |> Map.get("edges") + |> List.last() + |> Map.get("cursor") + + variables3 = %{ + "hash" => to_string(address.hash), + "first" => 3, + "after" => last_cursor_page2 + } + + conn = post(conn, "/graphql", query: query2, variables: variables3) + + %{"data" => %{"address" => %{"transactions" => page3}}} = json_response(conn, 200) + + assert page3["page_info"] == %{"has_next_page" => false, "has_previous_page" => true} + assert Enum.all?(page3["edges"], &(&1["node"]["block_number"] == first_block.number)) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/addresses_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/addresses_test.exs new file mode 100644 index 0000000..f6145a4 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/schema/query/addresses_test.exs @@ -0,0 +1,185 @@ +defmodule BlockScoutWeb.Schema.Query.AddressesTest do + use BlockScoutWeb.ConnCase + + describe "addresses field" do + test "with valid argument 'hashes', returns all expected fields", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 100) + + query = """ + query ($hashes: [AddressHash!]!) { + addresses(hashes: $hashes) { + hash + fetched_coin_balance + fetched_coin_balance_block_number + contract_code + } + } + """ + + variables = %{"hashes" => to_string(address.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "addresses" => [ + %{ + "hash" => to_string(address.hash), + "fetched_coin_balance" => to_string(address.fetched_coin_balance.value), + "fetched_coin_balance_block_number" => address.fetched_coin_balance_block_number, + "contract_code" => nil + } + ] + } + } + end + + test "with contract address, `contract_code` is serialized as expected", %{conn: conn} do + address = insert(:contract_address, fetched_coin_balance: 100) + + query = """ + query ($hashes: [AddressHash!]!) { + addresses(hashes: $hashes) { + contract_code + } + } + """ + + variables = %{"hashes" => to_string(address.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "addresses" => [ + %{ + "contract_code" => to_string(address.contract_code) + } + ] + } + } + end + + test "smart_contract returns all expected fields", %{conn: conn} do + address = insert(:address, fetched_coin_balance: 100) + smart_contract = insert(:smart_contract, address_hash: address.hash, contract_code_md5: "123") + + query = """ + query ($hashes: [AddressHash!]!) { + addresses(hashes: $hashes) { + fetched_coin_balance + smart_contract { + name + compiler_version + optimization + contract_source_code + abi + address_hash + } + } + } + """ + + variables = %{"hashes" => to_string(address.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "addresses" => [ + %{ + "fetched_coin_balance" => to_string(address.fetched_coin_balance.value), + "smart_contract" => %{ + "name" => smart_contract.name, + "compiler_version" => smart_contract.compiler_version, + "optimization" => smart_contract.optimization, + "contract_source_code" => smart_contract.contract_source_code, + "abi" => Jason.encode!(smart_contract.abi), + "address_hash" => to_string(address.hash) + } + } + ] + } + } + end + + test "errors for non-existent address hashes", %{conn: conn} do + address = build(:address) + + query = """ + query ($hashes: [AddressHash!]!) { + addresses(hashes: $hashes) { + fetched_coin_balance + } + } + """ + + variables = %{"hashes" => [to_string(address.hash)]} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] =~ ~s(Addresses not found.) + end + + test "errors if argument 'hashes' is missing", %{conn: conn} do + query = """ + query { + addresses { + fetched_coin_balance + } + } + """ + + variables = %{} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] == ~s(In argument "hashes": Expected type "[AddressHash!]!", found null.) + end + + test "errors if argument 'hashes' is not a list of address hashes", %{conn: conn} do + query = """ + query ($hashes: [AddressHash!]!) { + addresses(hashes: $hashes) { + fetched_coin_balance + } + } + """ + + variables = %{"hashes" => ["someInvalidHash"]} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] =~ ~s(Argument "hashes" has invalid value) + end + + test "correlates complexity to size of 'hashes' argument", %{conn: conn} do + # max of 50 addresses with four fields of complexity 1 can be fetched + # per query: + # 50 * 4 = 200, which is equal to a max complexity of 200 + hashes = 51 |> build_list(:address) |> Enum.map(&to_string(&1.hash)) + + query = """ + query ($hashes: [AddressHash!]!) { + addresses(hashes: $hashes) { + hash + fetched_coin_balance + fetched_coin_balance_block_number + contract_code + } + } + """ + + variables = %{"hashes" => hashes} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error1, error2]} = json_response(conn, 200) + assert error1["message"] =~ ~s(Field addresses is too complex) + assert error2["message"] =~ ~s(Operation is too complex) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/block_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/block_test.exs new file mode 100644 index 0000000..d601e3d --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/schema/query/block_test.exs @@ -0,0 +1,108 @@ +defmodule BlockScoutWeb.Schema.Query.BlockTest do + use BlockScoutWeb.ConnCase + + describe "block field" do + test "with valid argument 'number', returns all expected fields", %{conn: conn} do + block = insert(:block) + + query = """ + query ($number: Int!) { + block(number: $number) { + hash + consensus + difficulty + gas_limit + gas_used + nonce + number + size + timestamp + total_difficulty + miner_hash + parent_hash + parent_hash + } + } + """ + + variables = %{"number" => block.number} + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "block" => %{ + "hash" => to_string(block.hash), + "consensus" => block.consensus, + "difficulty" => to_string(block.difficulty), + "gas_limit" => to_string(block.gas_limit), + "gas_used" => to_string(block.gas_used), + "nonce" => to_string(block.nonce), + "number" => block.number, + "size" => block.size, + "timestamp" => DateTime.to_iso8601(block.timestamp), + "total_difficulty" => to_string(block.total_difficulty), + "miner_hash" => to_string(block.miner_hash), + "parent_hash" => to_string(block.parent_hash) + } + } + } + end + + test "errors for non-existent block number", %{conn: conn} do + block = insert(:block) + non_existent_block_number = block.number + 1 + + query = """ + query ($number: Int!) { + block(number: $number) { + number + } + } + """ + + variables = %{"number" => non_existent_block_number} + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] =~ ~s(Block number #{non_existent_block_number} was not found) + end + + test "errors if argument 'number' is missing", %{conn: conn} do + insert(:block) + + query = """ + { + block { + number + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] == ~s(In argument "number": Expected type "Int!", found null.) + end + + test "errors if argument 'number' is not an integer", %{conn: conn} do + insert(:block) + + query = """ + query ($number: Int!) { + block(number: $number) { + number + } + } + """ + + variables = %{"number" => "invalid"} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] =~ ~s(Argument "number" has invalid value) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs new file mode 100644 index 0000000..4638e2f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs @@ -0,0 +1,209 @@ +defmodule BlockScoutWeb.Schema.Query.NodeTest do + use BlockScoutWeb.ConnCase + + describe "node field" do + test "with valid argument 'id' for a transaction", %{conn: conn} do + transaction = insert(:transaction) + + query = """ + query($id: ID!) { + node(id: $id) { + ... on Transaction { + id + hash + } + } + } + """ + + id = Base.encode64("Transaction:#{transaction.hash}") + + variables = %{"id" => id} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "node" => %{ + "id" => id, + "hash" => to_string(transaction.hash) + } + } + } + end + + test "with 'id' for non-existent transaction", %{conn: conn} do + transaction = build(:transaction) + + query = """ + query($id: ID!) { + node(id: $id) { + ... on Transaction { + id + hash + } + } + } + """ + + id = Base.encode64("Transaction:#{transaction.hash}") + + variables = %{"id" => id} + + conn = get(conn, "/graphql", query: query, variables: variables) + + %{"errors" => [error]} = json_response(conn, 200) + + assert error["message"] == "Transaction not found." + end + + test "with valid argument 'id' for an internal transaction", %{conn: conn} do + transaction = insert(:transaction) |> with_block() + + internal_transaction = + insert(:internal_transaction, + transaction: transaction, + index: 0, + block_hash: transaction.block_hash, + block_index: 0 + ) + + query = """ + query($id: ID!) { + node(id: $id) { + ... on InternalTransaction { + id + transaction_hash + index + } + } + } + """ + + id = + %{transaction_hash: to_string(transaction.hash), index: internal_transaction.index} + |> Jason.encode!() + |> (fn unique_id -> "InternalTransaction:#{unique_id}" end).() + |> Base.encode64() + + variables = %{"id" => id} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "node" => %{ + "id" => id, + "transaction_hash" => to_string(transaction.hash), + "index" => internal_transaction.index + } + } + } + end + + test "with 'id' for non-existent internal transaction", %{conn: conn} do + transaction = insert(:transaction) |> with_block() + + internal_transaction = + build(:internal_transaction, + transaction: transaction, + index: 0, + block_hash: transaction.block_hash, + block_index: 0 + ) + + query = """ + query($id: ID!) { + node(id: $id) { + ... on InternalTransaction { + id + transaction_hash + index + } + } + } + """ + + id = + %{transaction_hash: to_string(transaction.hash), index: internal_transaction.index} + |> Jason.encode!() + |> (fn unique_id -> "InternalTransaction:#{unique_id}" end).() + |> Base.encode64() + + variables = %{"id" => id} + + conn = get(conn, "/graphql", query: query, variables: variables) + + %{"errors" => [error]} = json_response(conn, 200) + + assert error["message"] == "Internal transaction not found." + end + + test "with valid argument 'id' for a token_transfer", %{conn: conn} do + transaction = insert(:transaction) + token_transfer = insert(:token_transfer, transaction: transaction) + + query = """ + query($id: ID!) { + node(id: $id) { + ... on TokenTransfer { + id + transaction_hash + log_index + } + } + } + """ + + id = + %{transaction_hash: to_string(token_transfer.transaction_hash), log_index: token_transfer.log_index} + |> Jason.encode!() + |> (fn unique_id -> "TokenTransfer:#{unique_id}" end).() + |> Base.encode64() + + variables = %{"id" => id} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "node" => %{ + "id" => id, + "transaction_hash" => to_string(token_transfer.transaction_hash), + "log_index" => token_transfer.log_index + } + } + } + end + + test "with id for non-existent token transfer", %{conn: conn} do + transaction = build(:transaction) + + query = """ + query($id: ID!) { + node(id: $id) { + ... on TokenTransfer { + id + transaction_hash + log_index + } + } + } + """ + + id = + %{transaction_hash: to_string(transaction.hash), log_index: 0} + |> Jason.encode!() + |> (fn unique_id -> "TokenTransfer:#{unique_id}" end).() + |> Base.encode64() + + variables = %{"id" => id} + + conn = get(conn, "/graphql", query: query, variables: variables) + + %{"errors" => [error]} = json_response(conn, 200) + + assert error["message"] == "Token transfer not found." + end + end +end 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 new file mode 100644 index 0000000..1b02348 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs @@ -0,0 +1,326 @@ +defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do + use BlockScoutWeb.ConnCase + + 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) + address_hash = to_string(token_transfer.token_contract_address_hash) + + query = """ + query ($token_contract_address_hash: AddressHash!, $first: Int!) { + token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { + edges { + node { + amount + block_number + log_index + token_id + from_address_hash + to_address_hash + token_contract_address_hash + transaction_hash + } + } + } + } + """ + + variables = %{ + "token_contract_address_hash" => address_hash, + "first" => 1 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "token_transfers" => %{ + "edges" => [ + %{ + "node" => %{ + "amount" => to_string(token_transfer.amount), + "block_number" => token_transfer.block_number, + "log_index" => token_transfer.log_index, + "token_id" => token_transfer.token_id, + "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), + "transaction_hash" => to_string(token_transfer.transaction_hash) + } + } + ] + } + } + } + end + + test "with token contract address with zero token transfers", %{conn: conn} do + address = insert(:contract_address) + + query = """ + query ($token_contract_address_hash: AddressHash!, $first: Int!) { + token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { + edges { + node { + amount + block_number + log_index + token_id + from_address_hash + to_address_hash + token_contract_address_hash + transaction_hash + } + } + } + } + """ + + variables = %{ + "token_contract_address_hash" => to_string(address.hash), + "first" => 10 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "token_transfers" => %{ + "edges" => [] + } + } + } + end + + test "complexity correlates to first or last argument", %{conn: conn} do + address = insert(:contract_address) + + query1 = """ + query ($token_contract_address_hash: AddressHash!, $first: Int!) { + token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { + edges { + node { + amount + from_address_hash + to_address_hash + } + } + } + } + """ + + variables1 = %{ + "token_contract_address_hash" => to_string(address.hash), + "first" => 55 + } + + response1 = + conn + |> post("/graphql", query: query1, variables: variables1) + |> json_response(200) + + %{"errors" => [response1_error1, response1_error2]} = response1 + + assert response1_error1["message"] =~ ~s(Field token_transfers is too complex) + assert response1_error2["message"] =~ ~s(Operation is too complex) + + query2 = """ + query ($token_contract_address_hash: AddressHash!, $last: Int!) { + token_transfers(token_contract_address_hash: $token_contract_address_hash, last: $last) { + edges { + node { + amount + from_address_hash + to_address_hash + } + } + } + } + """ + + variables2 = %{ + "token_contract_address_hash" => to_string(address.hash), + "last" => 55 + } + + response2 = + conn + |> post("/graphql", query: query2, variables: variables2) + |> json_response(200) + + %{"errors" => [response2_error1, response2_error2]} = response2 + assert response2_error1["message"] =~ ~s(Field token_transfers is too complex) + assert response2_error2["message"] =~ ~s(Operation is too complex) + end + + test "with 'last' and 'count' arguments", %{conn: conn} do + # "`last: N` must always be acompanied by either a `before:` argument to + # the query, or an explicit `count:` option to the `from_query` call. + # Otherwise it is impossible to derive the required offset." + # https://hexdocs.pm/absinthe_relay/Absinthe.Relay.Connection.html#from_query/4 + # + # This test ensures support for a 'count' argument. + + address = insert(:contract_address) + + blocks = insert_list(2, :block) + + [transaction1, transaction2] = + for block <- blocks do + :transaction + |> insert() + |> with_block(block) + end + + token_transfer_attrs1 = %{ + block_number: transaction1.block_number, + transaction: transaction1, + token_contract_address: address + } + + token_transfer_attrs2 = %{ + block_number: transaction2.block_number, + transaction: transaction2, + token_contract_address: address + } + + insert(:token_transfer, token_transfer_attrs1) + insert(:token_transfer, token_transfer_attrs2) + + query = """ + query ($token_contract_address_hash: AddressHash!, $last: Int!, $count: Int) { + token_transfers(token_contract_address_hash: $token_contract_address_hash, last: $last, count: $count) { + edges { + node { + transaction_hash + } + } + } + } + """ + + variables = %{ + "token_contract_address_hash" => to_string(address.hash), + "last" => 1, + "count" => 2 + } + + [token_transfer] = + conn + |> post("/graphql", query: query, variables: variables) + |> json_response(200) + |> get_in(["data", "token_transfers", "edges"]) + + assert token_transfer["node"]["transaction_hash"] == to_string(transaction1.hash) + end + + test "pagination support with 'first' and 'after' arguments", %{conn: conn} do + address = insert(:contract_address) + + blocks = insert_list(3, :block) + + [transaction1, transaction2, transaction3] = + transactions = + for block <- blocks do + :transaction + |> insert() + |> with_block(block) + end + + for transaction <- transactions do + token_transfer_attrs = %{ + block_number: transaction.block_number, + transaction: transaction, + token_contract_address: address + } + + insert(:token_transfer, token_transfer_attrs) + end + + query1 = """ + query ($token_contract_address_hash: AddressHash!, $first: Int!) { + token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first) { + page_info { + has_next_page + has_previous_page + } + edges { + node { + transaction_hash + } + cursor + } + } + } + """ + + variables1 = %{ + "token_contract_address_hash" => to_string(address.hash), + "first" => 1 + } + + conn = post(conn, "/graphql", query: query1, variables: variables1) + + %{"data" => %{"token_transfers" => page1}} = json_response(conn, 200) + + assert page1["page_info"] == %{"has_next_page" => true, "has_previous_page" => false} + assert Enum.all?(page1["edges"], &(&1["node"]["transaction_hash"] == to_string(transaction3.hash))) + + last_cursor_page1 = + page1 + |> Map.get("edges") + |> List.last() + |> Map.get("cursor") + + query2 = """ + query ($token_contract_address_hash: AddressHash!, $first: Int!, $after: String!) { + token_transfers(token_contract_address_hash: $token_contract_address_hash, first: $first, after: $after) { + page_info { + has_next_page + has_previous_page + } + edges { + node { + transaction_hash + } + cursor + } + } + } + """ + + variables2 = %{ + "token_contract_address_hash" => to_string(address.hash), + "first" => 1, + "after" => last_cursor_page1 + } + + conn = post(conn, "/graphql", query: query2, variables: variables2) + + %{"data" => %{"token_transfers" => page2}} = json_response(conn, 200) + + assert page2["page_info"] == %{"has_next_page" => true, "has_previous_page" => true} + assert Enum.all?(page2["edges"], &(&1["node"]["transaction_hash"] == to_string(transaction2.hash))) + + last_cursor_page2 = + page2 + |> Map.get("edges") + |> List.last() + |> Map.get("cursor") + + variables3 = %{ + "token_contract_address_hash" => to_string(address.hash), + "first" => 1, + "after" => last_cursor_page2 + } + + conn = post(conn, "/graphql", query: query2, variables: variables3) + + %{"data" => %{"token_transfers" => page3}} = json_response(conn, 200) + + assert page3["page_info"] == %{"has_next_page" => false, "has_previous_page" => true} + assert Enum.all?(page3["edges"], &(&1["node"]["transaction_hash"] == to_string(transaction1.hash))) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs new file mode 100644 index 0000000..8135ee5 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs @@ -0,0 +1,552 @@ +defmodule BlockScoutWeb.Schema.Query.TransactionTest do + use BlockScoutWeb.ConnCase + + describe "transaction field" do + test "with valid argument 'hash', returns all expected fields", %{conn: conn} do + block = insert(:block) + + transaction = + :transaction + |> insert() + |> with_block(block, status: :ok) + + query = """ + query ($hash: FullHash!) { + transaction(hash: $hash) { + hash + block_number + cumulative_gas_used + error + gas + gas_price + gas_used + index + input + nonce + r + s + status + v + value + from_address_hash + to_address_hash + created_contract_address_hash + } + } + """ + + variables = %{"hash" => to_string(transaction.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "transaction" => %{ + "hash" => to_string(transaction.hash), + "block_number" => transaction.block_number, + "cumulative_gas_used" => to_string(transaction.cumulative_gas_used), + "error" => transaction.error, + "gas" => to_string(transaction.gas), + "gas_price" => to_string(transaction.gas_price.value), + "gas_used" => to_string(transaction.gas_used), + "index" => transaction.index, + "input" => to_string(transaction.input), + "nonce" => to_string(transaction.nonce), + "r" => to_string(transaction.r), + "s" => to_string(transaction.s), + "status" => transaction.status |> to_string() |> String.upcase(), + "v" => to_string(transaction.v), + "value" => to_string(transaction.value.value), + "from_address_hash" => to_string(transaction.from_address_hash), + "to_address_hash" => to_string(transaction.to_address_hash), + "created_contract_address_hash" => nil + } + } + } + end + + test "errors for non-existent transaction hash", %{conn: conn} do + transaction = build(:transaction) + + query = """ + query ($hash: FullHash!) { + transaction(hash: $hash) { + status + } + } + """ + + variables = %{"hash" => to_string(transaction.hash)} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] == "Transaction not found." + end + + test "errors if argument 'hash' is missing", %{conn: conn} do + query = """ + { + transaction { + status + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] == ~s(In argument "hash": Expected type "FullHash!", found null.) + end + + test "errors if argument 'hash' is not a 'FullHash'", %{conn: conn} do + query = """ + query ($hash: FullHash!) { + transaction(hash: $hash) { + status + } + } + """ + + variables = %{"hash" => "0x000"} + + conn = get(conn, "/graphql", query: query, variables: variables) + + assert %{"errors" => [error]} = json_response(conn, 200) + assert error["message"] =~ ~s(Argument "hash" has invalid value) + end + end + + describe "transaction internal_transactions field" do + test "returns all expected internal_transaction fields", %{conn: conn} do + address = insert(:address) + contract_address = insert(:contract_address) + + block = insert(:block) + + transaction = + :transaction + |> insert(from_address: address) + |> with_contract_creation(contract_address) + |> with_block(block) + + internal_transaction_attributes = %{ + transaction: transaction, + index: 0, + from_address: address, + call_type: :call, + block_hash: transaction.block_hash, + block_index: 0 + } + + internal_transaction = + :internal_transaction_create + |> insert(internal_transaction_attributes) + |> with_contract_creation(contract_address) + + query = """ + query ($hash: FullHash!, $first: Int!) { + transaction(hash: $hash) { + internal_transactions(first: $first) { + edges { + node { + call_type + created_contract_code + error + gas + gas_used + index + init + input + output + trace_address + type + value + block_number + transaction_index + created_contract_address_hash + from_address_hash + to_address_hash + transaction_hash + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(transaction.hash), + "first" => 1 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "transaction" => %{ + "internal_transactions" => %{ + "edges" => [ + %{ + "node" => %{ + "call_type" => internal_transaction.call_type |> to_string() |> String.upcase(), + "created_contract_code" => to_string(internal_transaction.created_contract_code), + "error" => internal_transaction.error, + "gas" => to_string(internal_transaction.gas), + "gas_used" => to_string(internal_transaction.gas_used), + "index" => internal_transaction.index, + "init" => to_string(internal_transaction.init), + "input" => nil, + "output" => nil, + "trace_address" => Jason.encode!(internal_transaction.trace_address), + "type" => internal_transaction.type |> to_string() |> String.upcase(), + "value" => to_string(internal_transaction.value.value), + "block_number" => internal_transaction.block_number, + "transaction_index" => internal_transaction.transaction_index, + "created_contract_address_hash" => + to_string(internal_transaction.created_contract_address_hash), + "from_address_hash" => to_string(internal_transaction.from_address_hash), + "to_address_hash" => nil, + "transaction_hash" => to_string(internal_transaction.transaction_hash) + } + } + ] + } + } + } + } + end + + test "with transaction with zero internal transactions", %{conn: conn} do + address = insert(:address) + + block = insert(:block) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block(block) + + query = """ + query ($hash: FullHash!, $first: Int!) { + transaction(hash: $hash) { + internal_transactions(first: $first) { + edges { + node { + index + transaction_hash + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(transaction.hash), + "first" => 1 + } + + conn = post(conn, "/graphql", query: query, variables: variables) + + assert json_response(conn, 200) == %{ + "data" => %{ + "transaction" => %{ + "internal_transactions" => %{ + "edges" => [] + } + } + } + } + end + + test "internal transactions are ordered by ascending index", %{conn: conn} do + transaction = insert(:transaction) |> with_block() + + insert(:internal_transaction, + transaction: transaction, + index: 2, + block_hash: transaction.block_hash, + block_index: 2 + ) + + insert(:internal_transaction, + transaction: transaction, + index: 0, + block_hash: transaction.block_hash, + block_index: 0 + ) + + insert(:internal_transaction, + transaction: transaction, + index: 1, + block_hash: transaction.block_hash, + block_index: 1 + ) + + query = """ + query ($hash: FullHash!, $first: Int!) { + transaction(hash: $hash) { + internal_transactions(first: $first) { + edges { + node { + index + transaction_hash + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(transaction.hash), + "first" => 3 + } + + response = + conn + |> post("/graphql", query: query, variables: variables) + |> json_response(200) + + internal_transactions = get_in(response, ["data", "transaction", "internal_transactions", "edges"]) + + index_order = Enum.map(internal_transactions, & &1["node"]["index"]) + + assert index_order == Enum.sort(index_order) + end + + test "complexity correlates to first or last argument", %{conn: conn} do + transaction = insert(:transaction) + + query1 = """ + query ($hash: FullHash!, $first: Int!) { + transaction(hash: $hash) { + internal_transactions(first: $first) { + edges { + node { + index + transaction_hash + } + } + } + } + } + """ + + variables1 = %{ + "hash" => to_string(transaction.hash), + "first" => 55 + } + + response1 = + conn + |> post("/graphql", query: query1, variables: variables1) + |> json_response(200) + + assert %{"errors" => [error1, error2, error3]} = response1 + assert error1["message"] =~ ~s(Field internal_transactions is too complex) + assert error2["message"] =~ ~s(Field transaction is too complex) + assert error3["message"] =~ ~s(Operation is too complex) + + query2 = """ + query ($hash: FullHash!, $last: Int!, $count: Int!) { + transaction(hash: $hash) { + internal_transactions(last: $last, count: $count) { + edges { + node { + index + transaction_hash + } + } + } + } + } + """ + + variables2 = %{ + "hash" => to_string(transaction.hash), + "last" => 55, + "count" => 100 + } + + response2 = + conn + |> post("/graphql", query: query2, variables: variables2) + |> json_response(200) + + assert %{"errors" => [error1, error2, error3]} = response2 + assert error1["message"] =~ ~s(Field internal_transactions is too complex) + assert error2["message"] =~ ~s(Field transaction is too complex) + assert error3["message"] =~ ~s(Operation is too complex) + end + + test "with 'last' and 'count' arguments", %{conn: conn} do + # "`last: N` must always be acompanied by either a `before:` argument to + # the query, or an explicit `count:` option to the `from_query` call. + # Otherwise it is impossible to derive the required offset." + # https://hexdocs.pm/absinthe_relay/Absinthe.Relay.Connection.html#from_query/4 + # + # This test ensures support for a 'count' argument. + + transaction = insert(:transaction) |> with_block() + + insert(:internal_transaction, + transaction: transaction, + index: 2, + block_hash: transaction.block_hash, + block_index: 2 + ) + + insert(:internal_transaction, + transaction: transaction, + index: 0, + block_hash: transaction.block_hash, + block_index: 0 + ) + + insert(:internal_transaction, + transaction: transaction, + index: 1, + block_hash: transaction.block_hash, + block_index: 1 + ) + + query = """ + query ($hash: FullHash!, $last: Int!, $count: Int!) { + transaction(hash: $hash) { + internal_transactions(last: $last, count: $count) { + edges { + node { + index + transaction_hash + } + } + } + } + } + """ + + variables = %{ + "hash" => to_string(transaction.hash), + "last" => 1, + "count" => 3 + } + + [internal_transaction] = + conn + |> post("/graphql", query: query, variables: variables) + |> json_response(200) + |> get_in(["data", "transaction", "internal_transactions", "edges"]) + + assert internal_transaction["node"]["index"] == 2 + end + + test "pagination support with 'first' and 'after' arguments", %{conn: conn} do + transaction = insert(:transaction) |> with_block() + + for index <- 0..5 do + insert(:internal_transaction_create, + transaction: transaction, + index: index, + block_hash: transaction.block_hash, + block_index: index + ) + end + + query1 = """ + query ($hash: FullHash!, $first: Int!) { + transaction(hash: $hash) { + internal_transactions(first: $first) { + page_info { + has_next_page + has_previous_page + } + edges { + node { + index + transaction_hash + } + cursor + } + } + } + } + """ + + variables1 = %{ + "hash" => to_string(transaction.hash), + "first" => 2 + } + + conn = post(conn, "/graphql", query: query1, variables: variables1) + + %{"data" => %{"transaction" => %{"internal_transactions" => page1}}} = json_response(conn, 200) + + assert page1["page_info"] == %{"has_next_page" => true, "has_previous_page" => false} + assert Enum.all?(page1["edges"], &(&1["node"]["index"] in 0..1)) + + last_cursor_page1 = + page1 + |> Map.get("edges") + |> List.last() + |> Map.get("cursor") + + query2 = """ + query ($hash: FullHash!, $first: Int!, $after: String!) { + transaction(hash: $hash) { + internal_transactions(first: $first, after: $after) { + page_info { + has_next_page + has_previous_page + } + edges { + node { + index + transaction_hash + } + cursor + } + } + } + } + """ + + variables2 = %{ + "hash" => to_string(transaction.hash), + "first" => 2, + "after" => last_cursor_page1 + } + + page2 = + conn + |> post("/graphql", query: query2, variables: variables2) + |> json_response(200) + |> get_in(["data", "transaction", "internal_transactions"]) + + assert page2["page_info"] == %{"has_next_page" => true, "has_previous_page" => true} + assert Enum.all?(page2["edges"], &(&1["node"]["index"] in 2..3)) + + last_cursor_page2 = + page2 + |> Map.get("edges") + |> List.last() + |> Map.get("cursor") + + variables3 = %{ + "hash" => to_string(transaction.hash), + "first" => 2, + "after" => last_cursor_page2 + } + + page3 = + conn + |> post("/graphql", query: query2, variables: variables3) + |> json_response(200) + |> get_in(["data", "transaction", "internal_transactions"]) + + assert page3["page_info"] == %{"has_next_page" => false, "has_previous_page" => true} + assert Enum.all?(page3["edges"], &(&1["node"]["index"] in 4..5)) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/schema/subscription/token_transfers_test.exs b/apps/block_scout_web/test/block_scout_web/schema/subscription/token_transfers_test.exs new file mode 100644 index 0000000..4383457 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/schema/subscription/token_transfers_test.exs @@ -0,0 +1,117 @@ +defmodule BlockScoutWeb.Schema.Subscription.TokenTransfersTest do + use BlockScoutWeb.SubscriptionCase + import Mox + + alias BlockScoutWeb.Notifier + + describe "token_transfers field" do + setup :set_mox_global + + setup do + configuration = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint) + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, pubsub_server: BlockScoutWeb.PubSub) + + :ok + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, configuration) + end) + end + + test "with valid argument, returns all expected fields", %{socket: socket} do + transaction = insert(:transaction) + token_transfer = insert(:token_transfer, transaction: transaction) + address_hash = to_string(token_transfer.token_contract_address_hash) + + subscription = """ + subscription ($hash: AddressHash!) { + token_transfers(token_contract_address_hash: $hash) { + amount + from_address_hash + to_address_hash + token_contract_address_hash + transaction_hash + } + } + """ + + variables = %{"hash" => address_hash} + + ref = push_doc(socket, subscription, variables: variables) + + assert_reply(ref, :ok, %{subscriptionId: subscription_id}) + + Notifier.handle_event({:chain_event, :token_transfers, :realtime, [token_transfer]}) + + expected = %{ + result: %{ + data: %{ + "token_transfers" => [ + %{ + "amount" => to_string(token_transfer.amount), + "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), + "transaction_hash" => to_string(token_transfer.transaction_hash) + } + ] + } + }, + subscriptionId: subscription_id + } + + assert_push("subscription:data", push) + assert push == expected + end + + test "ignores irrelevant tokens", %{socket: socket} do + transaction = insert(:transaction) + [token_transfer1, token_transfer2] = insert_list(2, :token_transfer, transaction: transaction) + address_hash1 = to_string(token_transfer1.token_contract_address_hash) + + subscription = """ + subscription ($hash: AddressHash!) { + token_transfers(token_contract_address_hash: $hash) { + amount + token_contract_address_hash + } + } + """ + + variables = %{"hash" => address_hash1} + + ref = push_doc(socket, subscription, variables: variables) + + assert_reply(ref, :ok, %{subscriptionId: _subscription_id}) + + Notifier.handle_event({:chain_event, :token_transfers, :realtime, [token_transfer2]}) + + refute_push("subscription:data", _push) + end + + test "ignores non-realtime updates", %{socket: socket} do + transaction = insert(:transaction) + token_transfer = insert(:token_transfer, transaction: transaction) + address_hash = to_string(token_transfer.token_contract_address_hash) + + subscription = """ + subscription ($hash: AddressHash!) { + token_transfers(token_contract_address_hash: $hash) { + amount + token_contract_address_hash + } + } + """ + + variables = %{"hash" => address_hash} + + ref = push_doc(socket, subscription, variables: variables) + + assert_reply(ref, :ok, %{subscriptionId: _subscription_id}) + + Notifier.handle_event({:chain_event, :token_transfers, :catchup, [token_transfer]}) + + refute_push("subscription:data", _push) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/social_media_test.exs b/apps/block_scout_web/test/block_scout_web/social_media_test.exs new file mode 100644 index 0000000..8a102a6 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/social_media_test.exs @@ -0,0 +1,25 @@ +defmodule BlockScoutWeb.SocialMediaTest do + use Explorer.DataCase + + alias BlockScoutWeb.SocialMedia + + test "it filters out unsupported services" do + Application.put_env( + :block_scout_web, + BlockScoutWeb.SocialMedia, + twitter: "MyTwitterProfile", + myspace: "MyAwesomeProfile" + ) + + links = SocialMedia.links() + assert Keyword.has_key?(links, :twitter) + refute Keyword.has_key?(links, :myspace) + end + + test "it prepends the service url" do + Application.put_env(:block_scout_web, BlockScoutWeb.SocialMedia, twitter: "MyTwitterProfile") + + links = SocialMedia.links() + assert links[:twitter] == "https://www.twitter.com/MyTwitterProfile" + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/abi_encoded_value_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/abi_encoded_value_view_test.exs new file mode 100644 index 0000000..2953bb8 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/abi_encoded_value_view_test.exs @@ -0,0 +1,127 @@ +defmodule BlockScoutWeb.ABIEncodedValueViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.ABIEncodedValueView + + defp value_html(type, value) do + type + |> ABIEncodedValueView.value_html(value) + |> case do + :error -> + raise "failed to generate html" + + other -> + other + end + |> Phoenix.HTML.Safe.to_iodata() + |> IO.iodata_to_binary() + end + + defp copy_text(type, value) do + type + |> ABIEncodedValueView.copy_text(value) + |> case do + :error -> + raise "failed to generate copy text" + + other -> + other + end + |> Phoenix.HTML.Safe.to_iodata() + |> IO.iodata_to_binary() + end + + describe "value_html/2" do + test "it formats addresses as links" do + address = "0x0000000000000000000000000000000000000000" + address_bytes = address |> String.trim_leading("0x") |> Base.decode16!() + + expected = ~s(#{address}) + + assert value_html("address", address_bytes) == expected + end + + test "it formats lists with newlines and spaces" do + expected = + String.trim(""" + [ + 1, + 2, + 3, + 4 + ] + """) + + assert value_html("uint[]", [1, 2, 3, 4]) == expected + end + + test "it formats nested lists with nested depth" do + expected = + String.trim(""" + [ + [ + 1, + 2 + ], + [ + 3, + 4 + ] + ] + """) + + assert value_html("uint[][]", [[1, 2], [3, 4]]) == expected + end + + test "it formats lists of addresses as a list of links" do + address = "0x0000000000000000000000000000000000000000" + address_link = ~s(#{address}) + + expected = + String.trim(""" + [ + #{address_link}, + #{address_link}, + #{address_link}, + #{address_link} + ] + """) + + address_bytes = "0x0000000000000000000000000000000000000000" |> String.trim_leading("0x") |> Base.decode16!() + + assert value_html("address[4]", [address_bytes, address_bytes, address_bytes, address_bytes]) == expected + end + + test "it renders :dynamic values as bytes" do + assert value_html("uint", {:dynamic, <<1>>}) == "0x01" + end + + test "it renders :tuple values as string" do + assert value_html("(uint256)", {123}) == "(123)" + end + end + + describe "copy_text/2" do + test "it skips link formatting of addresses" do + address = "0x0000000000000000000000000000000000000000" + address_bytes = address |> String.trim_leading("0x") |> Base.decode16!() + + assert copy_text("address", address_bytes) == address + end + + test "it skips the formatting when copying lists" do + assert copy_text("uint[4]", [1, 2, 3, 4]) == "[1, 2, 3, 4]" + end + + test "it copies bytes as their hex representation" do + hex = "0xffffff" + bytes = hex |> String.trim_leading("0x") |> Base.decode16!(case: :lower) + + assert copy_text("bytes", bytes) == hex + end + + test "it copies :dynamic values as bytes" do + assert copy_text("uint", {:dynamic, <<1>>}) == "0x01" + end + end +end 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 new file mode 100644 index 0000000..bc07e1c --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/address_coin_balance_view_test.exs @@ -0,0 +1,62 @@ +defmodule BlockScoutWeb.AddressCoinBalanceViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.AddressCoinBalanceView + alias Explorer.Chain.Wei + + describe "format/1" 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 ETH" + end + + test "format negative values" do + wei = Wei.from(Decimal.new(-1_340_000_000), :gwei) + + assert AddressCoinBalanceView.format(wei) == "-1.34 ETH" + end + end + + describe "delta_arrow/1" do + test "return up pointing arrow for positive value" do + value = Decimal.new(123) + + assert AddressCoinBalanceView.delta_arrow(value) == "▲" + end + + test "return down pointing arrow for negative value" do + value = Decimal.new(-123) + + assert AddressCoinBalanceView.delta_arrow(value) == "â–ŧ" + end + end + + describe "delta_sign/1" do + test "return Positive for positive value" do + value = Decimal.new(123) + + assert AddressCoinBalanceView.delta_sign(value) == "Positive" + end + + test "return Negative for negative value" do + value = Decimal.new(-123) + + assert AddressCoinBalanceView.delta_sign(value) == "Negative" + end + end + + describe "format_delta/1" do + test "format positive values" do + value = Decimal.new(1_340_000_000_000_000_000) + + 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 ETH" + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_contract_view_test.exs new file mode 100644 index 0000000..189c740 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/address_contract_view_test.exs @@ -0,0 +1,79 @@ +defmodule BlockScoutWeb.AddressContractViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.AddressContractView + + doctest BlockScoutWeb.AddressContractView + + describe "format_optimization_text/1" do + test "returns \"true\" for the boolean true" do + assert AddressContractView.format_optimization_text(true) == "true" + end + + test "returns \"false\" for the boolean false" do + assert AddressContractView.format_optimization_text(false) == "false" + end + end + + describe "contract_lines_with_index/1" do + test "returns a list of tuples containing two strings each" do + code = """ + pragma solidity >=0.4.22 <0.6.0; + + struct Proposal { + uint voteCount; + } + + address chairperson; + mapping(address => Voter) voters; + Proposal[] proposals; + + constructor(uint8 _numProposals) public { + chairperson = msg.sender; + voters[chairperson].weight = 1; + proposals.length = _numProposals; + } + """ + + result = AddressContractView.contract_lines_with_index(code) + + assert result == [ + {"pragma solidity >=0.4.22 <0.6.0;", " 1"}, + {"", " 2"}, + {"struct Proposal {", " 3"}, + {" uint voteCount;", " 4"}, + {"}", " 5"}, + {"", " 6"}, + {"address chairperson;", " 7"}, + {"mapping(address => Voter) voters;", " 8"}, + {"Proposal[] proposals;", " 9"}, + {"", "10"}, + {"constructor(uint8 _numProposals) public {", "11"}, + {" chairperson = msg.sender;", "12"}, + {" voters[chairperson].weight = 1;", "13"}, + {" proposals.length = _numProposals;", "14"}, + {"}", "15"}, + {"", "16"} + ] + end + + test "returns a list of tuples and the second element always has n chars with x lines" do + chars = 3 + lines = 100 + result = AddressContractView.contract_lines_with_index(Enum.join(1..lines, "\n")) + assert Enum.all?(result, fn {_, number} -> String.length(number) == chars end) + end + + test "returns a list of tuples and the first element is just a line from the original string" do + result = AddressContractView.contract_lines_with_index("a\nb\nc\nd\ne") + + assert Enum.map(result, fn {line, _number} -> line end) == [ + "a", + "b", + "c", + "d", + "e" + ] + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs new file mode 100644 index 0000000..abac26b --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs @@ -0,0 +1,110 @@ +defmodule BlockScoutWeb.AddressDecompiledContractViewTest do + use Explorer.DataCase + + alias BlockScoutWeb.AddressDecompiledContractView + + describe "highlight_decompiled_code/1" do + test "generate correct html code" do + code = """ + # + # eveem.org 6 Feb 2019 + # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875 + # + # Let's make the world open source + #  + # + # I failed with these: + # - unknowne77c646d(?) + # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data) + # All the rest is below. + # + + + # Storage definitions and getters + + def storage: + allowance is uint256 => uint256 # mask(256, 0) at storage #2 + stor4 is uint256 => uint8 # mask(8, 0) at storage #4 + + def allowance(address _owner, address _spender) payable:  + require (calldata.size - 4) >= 64 + return allowance[sha3(((320 - 1) and (320 - 1) and _owner), 1), ((320 - 1) and _spender and (320 - 1))] + + + # + # Regular functions - see Tutorial for understanding quirks of the code + # + + + # folder failed in this function - may be terribly long, sorry + def unknownc47d033b(?) payable:  + if (calldata.size - 4) < 32: + revert + else: + if not (320 - 1) or not cd[4]: + revert + else: + mem[0] = (320 - 1) and (320 - 1) and cd[4] + mem[32] = 4 + mem[96] = bool(stor4[((320 - 1) and (320 - 1) and cd[4])]) + return bool(stor4[((320 - 1) and (320 - 1) and cd[4])]) + + def _fallback() payable: # default function + revert + """ + + result = AddressDecompiledContractView.highlight_decompiled_code(code) + + assert result == + " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n #\n # I failed with these:\n # - unknowne77c646d(?)\n # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)\n # All the rest is below.\n #\n\n\n # Storage definitions and getters\n\n def storage:\n allowance is uint256 => uint256 # mask(256, 0) at storage #2\n stor4 is uint256 => uint8 # mask(8, 0) at storage #4\n\n def allowance(address _owner, address _spender) payable: \n require (calldata.size - 4) >= 64\n return allowance[sha3(((320 - 1) and (320 - 1) and _owner), 1), ((320 - 1) and _spender and (320 - 1))]\n\n\n #\n # Regular functions - see Tutorial for understanding quirks of the code\n #\n\n\n # folder failed in this function - may be terribly long, sorry\n def unknownc47d033b(?) payable: \n if (calldata.size - 4) < 32:\n revert\n else:\n if not (320 - 1) or not cd[4]:\n revert\n else:\n mem[0] = (320 - 1) and (320 - 1) and cd[4]\n mem[32] = 4\n mem[96] = bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n return bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n\n def _fallback() payable: # default function\n revert\n\n\n" + end + + test "adds style span to every line" do + code = """ + # + # eveem.org 6 Feb 2019 + # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875 + # + # Let's make the world open source + #  + """ + + assert AddressDecompiledContractView.highlight_decompiled_code(code) == + " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n\n\n" + end + + test "does not remove bold text" do + code = """ + # + # Eveem.org 26 Apr 2019 + # Decompiled source of 0x06012c8cf97bead5deae237070f9587f8e7a266d + # + # Let's make the world open source + #  + + const name = 'CryptoKitties' + const symbol = 'CK' + const GEN0_STARTING_PRICE = 10^16 + const GEN0_AUCTION_DURATION = (24 * 3600) + const GEN0_CREATION_LIMIT = 45000 + const PROMO_CREATION_LIMIT = 5000 + + """ + + assert AddressDecompiledContractView.highlight_decompiled_code(code) == + "#\n# Eveem.org 26 Apr 2019\n# Decompiled source of 0x06012c8cf97bead5deae237070f9587f8e7a266d\n#\n# Let's make the world open source\n# \n\nconst name = 'CryptoKitties'\nconst symbol = 'CK'\nconst GEN0_STARTING_PRICE = 10^16\nconst GEN0_AUCTION_DURATION = (24 * 3600)\nconst GEN0_CREATION_LIMIT = 45000\nconst PROMO_CREATION_LIMIT = 5000\n\n\n\n" + end + end + + describe "last_decompiled_contract_version/1" do + test "returns last version" do + contract2 = insert(:decompiled_smart_contract, decompiler_version: "v2") + contract1 = insert(:decompiled_smart_contract, decompiler_version: "v1") + contract3 = insert(:decompiled_smart_contract, decompiler_version: "v3") + + result = AddressDecompiledContractView.last_decompiled_contract_version([contract2, contract1, contract3]) + + assert result == contract3 + 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 new file mode 100644 index 0000000..1ac75c9 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs @@ -0,0 +1,182 @@ +defmodule BlockScoutWeb.AddressTokenBalanceViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.AddressTokenBalanceView + alias Explorer.Chain + + describe "tokens_count_title/1" do + test "returns the title pluralized" do + token_balances = [ + build(:token_balance), + build(:token_balance) + ] + + assert AddressTokenBalanceView.tokens_count_title(token_balances) == "2 tokens" + end + end + + describe "filter_by_type/2" do + test "filter tokens by the given type" 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_a.token}, {token_balance_b, token_balance_b.token}] + + assert AddressTokenBalanceView.filter_by_type(token_balances, "ERC-20") == [ + {token_balance_a, token_balance_a.token} + ] + end + end + + describe "sort_by_name/1" do + test "sorts the given tokens by its name" do + token_balance_a = build(:token_balance, token: build(:token, name: "token name")) + token_balance_b = build(:token_balance, token: build(:token, name: "token")) + token_balance_c = build(:token_balance, token: build(:token, name: "atoken")) + + token_balances = [ + token_balance_a, + token_balance_b, + token_balance_c + ] + + expected = [token_balance_c, token_balance_b, token_balance_a] + + assert AddressTokenBalanceView.sort_by_name(token_balances) == expected + end + + test "considers nil values in the bottom of the list" do + token_balance_a = build(:token_balance, token: build(:token, name: nil)) + token_balance_b = build(:token_balance, token: build(:token, name: "token name")) + token_balance_c = build(:token_balance, token: build(:token, name: "token")) + + token_balances = [ + token_balance_a, + token_balance_b, + token_balance_c + ] + + expected = [token_balance_c, token_balance_b, token_balance_a] + + assert AddressTokenBalanceView.sort_by_name(token_balances) == expected + end + + test "considers capitalization" do + token_balance_a = build(:token_balance, token: build(:token, name: "Token")) + token_balance_b = build(:token_balance, token: build(:token, name: "atoken")) + + token_balances = [token_balance_a, token_balance_b] + expected = [token_balance_b, token_balance_a] + + assert AddressTokenBalanceView.sort_by_name(token_balances) == expected + end + end + + describe "sort_by_usd_value_and_name/1" do + test "sorts the given tokens by its name and usd_value" do + token_balance_a = + build(:token_balance, + token: build(:token, name: "token name", decimals: Decimal.new(18)) |> Map.put(:usd_value, Decimal.new(2)), + value: Decimal.new(100_500) + ) + + token_balance_b = + build(:token_balance, + token: + build(:token, name: "token", decimals: Decimal.new(18)) |> Map.put(:usd_value, Decimal.from_float(3.45)), + value: Decimal.new(100_500) + ) + + token_balance_c = + build(:token_balance, + token: build(:token, name: nil, decimals: Decimal.new(18)) |> Map.put(:usd_value, Decimal.new(2)), + value: Decimal.new(100_500) + ) + + token_balance_d = + build(:token_balance, + token: build(:token, name: "Atoken", decimals: Decimal.new(18)) |> Map.put(:usd_value, Decimal.new(1)), + value: Decimal.new(100_500) + ) + + token_balance_e = + build(:token_balance, + token: build(:token, name: "atoken", decimals: Decimal.new(18)) |> Map.put(:usd_value, nil), + value: Decimal.new(100_500) + ) + + token_balance_f = + build(:token_balance, + token: build(:token, name: "Btoken", decimals: Decimal.new(18)) |> Map.put(:usd_value, nil), + value: Decimal.new(100_500) + ) + + token_balance_g = + build(:token_balance, + token: build(:token, name: "Btoken", decimals: Decimal.new(18)) |> Map.put(:usd_value, Decimal.new(1)), + value: Decimal.new(100_500) + ) + + token_balances = [ + {token_balance_a, token_balance_a.token}, + {token_balance_b, token_balance_b.token}, + {token_balance_c, token_balance_c.token}, + {token_balance_d, token_balance_d.token}, + {token_balance_e, token_balance_e.token}, + {token_balance_f, token_balance_f.token}, + {token_balance_g, token_balance_g.token} + ] + + expected = [ + {token_balance_b, token_balance_b.token}, + {token_balance_a, token_balance_a.token}, + {token_balance_c, token_balance_c.token}, + {token_balance_d, token_balance_d.token}, + {token_balance_g, token_balance_g.token}, + {token_balance_e, token_balance_e.token}, + {token_balance_f, token_balance_f.token} + ] + + assert AddressTokenBalanceView.sort_by_usd_value_and_name(token_balances) == expected + end + end + + describe "balance_in_usd/1" do + test "return balance in usd" do + token = + :token + |> build(decimals: Decimal.new(0)) + |> Map.put(:usd_value, Decimal.new(3)) + + token_balance = build(:token_balance, value: Decimal.new(10), token: token) + + result = Chain.balance_in_usd(token_balance) + + assert Decimal.compare(result, 30) == :eq + end + + test "return nil if usd_value is not present" do + token = + :token + |> build(decimals: Decimal.new(0)) + |> Map.put(:usd_value, nil) + + token_balance = build(:token_balance, value: 10, token: token) + + assert Chain.balance_in_usd(token_balance) == nil + end + + test "consider decimals when computing value" do + token = + :token + |> build(decimals: Decimal.new(2)) + |> Map.put(:usd_value, Decimal.new(3)) + + token_balance = build(:token_balance, value: Decimal.new(10), token: token) + + result = Chain.balance_in_usd(token_balance) + + assert Decimal.compare(result, Decimal.from_float(0.3)) == :eq + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_transaction_view_test.exs new file mode 100644 index 0000000..666f06b --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/address_transaction_view_test.exs @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.AddressTransactionViewTest do + use BlockScoutWeb.ConnCase, async: true + + doctest BlockScoutWeb.AddressTransactionView +end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs new file mode 100644 index 0000000..c8e2393 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs @@ -0,0 +1,393 @@ +defmodule BlockScoutWeb.AddressViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias Explorer.Chain.{Address, Data, Hash, Transaction} + alias BlockScoutWeb.{AddressView, Endpoint} + + describe "address_partial_selector/4" do + test "for a pending transaction contract creation to address" do + transaction = insert(:transaction, to_address: nil, created_contract_address_hash: nil) + assert AddressView.address_partial_selector(transaction, :to, nil) == "Contract Address Pending" + end + + test "for a pending internal transaction contract creation to address" do + transaction = insert(:transaction, to_address: nil) |> with_block() + + internal_transaction = + insert(:internal_transaction, + index: 1, + transaction: transaction, + to_address: nil, + created_contract_address_hash: nil, + block_hash: transaction.block_hash, + block_index: 1 + ) + + assert "Contract Address Pending" == AddressView.address_partial_selector(internal_transaction, :to, nil) + end + + test "will truncate address" do + transaction = %Transaction{to_address: to_address} = insert(:transaction) + + assert [ + view_module: AddressView, + partial: "_link.html", + address: ^to_address, + contract: false, + truncate: true, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :to, nil, true) + end + + test "for a non-contract to address not on address page" do + transaction = %Transaction{to_address: to_address} = insert(:transaction) + + assert [ + view_module: AddressView, + partial: "_link.html", + address: ^to_address, + contract: false, + truncate: false, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :to, nil) + end + + test "for a non-contract to address non matching address page" do + transaction = %Transaction{to_address: to_address} = insert(:transaction) + + assert [ + view_module: AddressView, + partial: "_link.html", + address: ^to_address, + contract: false, + truncate: false, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :to, nil) + end + + test "for a non-contract to address matching address page" do + transaction = %Transaction{to_address: to_address} = insert(:transaction) + + assert [ + view_module: AddressView, + partial: "_responsive_hash.html", + address: ^to_address, + contract: false, + truncate: false, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :to, transaction.to_address) + end + + test "for a contract to address non matching address page" do + contract_address = insert(:contract_address) + transaction = insert(:transaction, to_address: nil, created_contract_address: contract_address) + + assert [ + view_module: AddressView, + partial: "_link.html", + address: ^contract_address, + contract: true, + truncate: false, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :to, transaction.to_address) + end + + test "for a contract to address matching address page" do + contract_address = insert(:contract_address) + transaction = insert(:transaction, to_address: nil, created_contract_address: contract_address) + + assert [ + view_module: AddressView, + partial: "_responsive_hash.html", + address: ^contract_address, + contract: true, + truncate: false, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :to, contract_address) + end + + test "for a non-contract from address not on address page" do + transaction = %Transaction{to_address: to_address} = insert(:transaction) + + assert [ + view_module: AddressView, + partial: "_link.html", + address: ^to_address, + contract: false, + truncate: false, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :to, nil) + end + + test "for a non-contract from address matching address page" do + transaction = %Transaction{from_address: from_address} = insert(:transaction) + + assert [ + view_module: AddressView, + partial: "_responsive_hash.html", + address: ^from_address, + contract: false, + truncate: false, + use_custom_tooltip: false + ] = AddressView.address_partial_selector(transaction, :from, transaction.from_address) + end + end + + describe "balance_block_number/1" do + test "gives empty string with no fetched balance block number present" do + assert AddressView.balance_block_number(%Address{}) == "" + end + + test "gives block number when fetched balance block number is non-nil" do + assert AddressView.balance_block_number(%Address{fetched_coin_balance_block_number: 1_000_000}) == "1000000" + end + end + + describe "balance_percentage_enabled/1" do + test "with non_zero market cap" do + Application.put_env(:block_scout_web, :show_percentage, true) + + assert AddressView.balance_percentage_enabled?(100_500) == true + end + + test "with zero market cap" do + Application.put_env(:block_scout_web, :show_percentage, true) + + assert AddressView.balance_percentage_enabled?(0) == false + end + + test "with switched off show_percentage" do + Application.put_env(:block_scout_web, :show_percentage, false) + + assert AddressView.balance_percentage_enabled?(100_501) == false + end + end + + test "balance_percentage/1" do + Application.put_env(:explorer, :supply, Explorer.Chain.Supply.ProofOfAuthority) + address = insert(:address, fetched_coin_balance: 2_524_608_000_000_000_000_000_000) + + assert "1.0000% Market Cap" = AddressView.balance_percentage(address) + end + + test "balance_percentage with nil total_supply" do + address = insert(:address, fetched_coin_balance: 2_524_608_000_000_000_000_000_000) + + assert "" = AddressView.balance_percentage(address, nil) + end + + describe "contract?/1" do + test "with a smart contract" do + {:ok, code} = Data.cast("0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef") + address = insert(:address, contract_code: code) + assert AddressView.contract?(address) + end + + test "with an account" do + address = insert(:address, contract_code: nil) + refute AddressView.contract?(address) + end + + test "with nil address" do + assert AddressView.contract?(nil) + end + end + + describe "hash/1" do + test "gives a string version of an address's hash" do + address = %Address{ + hash: %Hash{ + byte_count: 20, + bytes: <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, 91>> + } + } + + assert AddressView.hash(address) == "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + end + end + + describe "primary_name/1" do + test "gives an address's primary name when present" do + address = insert(:address) + + address_name = insert(:address_name, address: address, primary: true, name: "POA Foundation Wallet") + insert(:address_name, address: address, name: "POA Wallet") + + preloaded_address = Explorer.Repo.preload(address, :names) + + assert AddressView.primary_name(preloaded_address) == address_name.name + end + + test "returns any when no primary available" do + address_name = insert(:address_name, name: "POA Wallet") + preloaded_address = Explorer.Repo.preload(address_name.address, :names) + + assert AddressView.primary_name(preloaded_address) == address_name.name + end + end + + describe "qr_code/1" do + test "it returns an encoded value" do + address = build(:address) + assert {:ok, _} = Base.decode64(AddressView.qr_code(address.hash)) + end + end + + describe "smart_contract_verified?/1" do + test "returns true when smart contract is verified" do + smart_contract = insert(:smart_contract, contract_code_md5: "123") + address = insert(:address, smart_contract: smart_contract) + + assert AddressView.smart_contract_verified?(address) + end + + test "returns false when smart contract is not verified" do + address = insert(:address, smart_contract: nil) + + refute AddressView.smart_contract_verified?(address) + end + end + + describe "smart_contract_with_read_only_functions?/1" do + test "returns true when abi has read only functions" do + smart_contract = + insert( + :smart_contract, + abi: [ + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ], + contract_code_md5: "123" + ) + + address = insert(:address, smart_contract: smart_contract) + + assert AddressView.smart_contract_with_read_only_functions?(address) + end + + test "returns false when there is no read only functions" do + smart_contract = + insert( + :smart_contract, + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + } + ], + contract_code_md5: "123" + ) + + address = insert(:address, smart_contract: smart_contract) + + refute AddressView.smart_contract_with_read_only_functions?(address) + end + + test "returns false when smart contract is not verified" do + address = insert(:address, smart_contract: nil) + + refute AddressView.smart_contract_with_read_only_functions?(address) + end + end + + describe "token_title/1" do + test "returns the 6 first and 6 last chars of address hash when token has no name" do + token = insert(:token, name: nil) + + hash = to_string(token.contract_address_hash) + expected_hash = String.slice(hash, 0, 8) <> "-" <> String.slice(hash, -6, 6) + assert expected_hash == AddressView.token_title(token) + end + + test "returns name(symbol) when token has name" do + token = insert(:token, name: "super token money", symbol: "ST$") + + assert AddressView.token_title(token) == "super token money (ST$)" + end + end + + describe "current_tab_name/1" do + test "generates the correct tab name for the token path" do + path = address_token_path(Endpoint, :index, "0x4ddr3s") + + assert AddressView.current_tab_name(path) == "Tokens" + end + + test "generates the correct tab name for the transactions path" do + path = address_transaction_path(Endpoint, :index, "0x4ddr3s") + + assert AddressView.current_tab_name(path) == "Transactions" + end + + test "generates the correct tab name for the internal transactions path" do + path = address_internal_transaction_path(Endpoint, :index, "0x4ddr3s") + + assert AddressView.current_tab_name(path) == "Internal Transactions" + end + + test "generates the correct tab name for the contracts path" do + path = address_contract_path(Endpoint, :index, "0x4ddr3s") + + assert AddressView.current_tab_name(path) == "Code" + end + + test "generates the correct tab name for the read_contract path" do + path = address_read_contract_path(Endpoint, :index, "0x4ddr3s") + + assert AddressView.current_tab_name(path) == "Read Contract" + end + + test "generates the correct tab name for the coin_balances path" do + path = address_coin_balance_path(Endpoint, :index, "0x4ddr3s") + + assert AddressView.current_tab_name(path) == "Coin Balance History" + end + + test "generates the correct tab name for the validations path" do + path = address_validation_path(Endpoint, :index, "0x4ddr3s") + + assert AddressView.current_tab_name(path) == "Blocks Validated" + end + end + + describe "short_hash/1" do + test "returns a shortened hash of 6 hex characters" do + address = insert(:address) + assert "0x" <> short_hash = AddressView.short_hash(address) + assert String.length(short_hash) == 6 + end + end + + describe "address_page_title/1" do + test "uses the Smart Contract name when the contract is verified" do + smart_contract = build(:smart_contract, name: "POA") + address = build(:address, smart_contract: smart_contract) + + assert AddressView.address_page_title(address) == "POA (#{address})" + end + + test "uses the string 'Contract' when it's a contract" do + address = build(:contract_address, smart_contract: nil) + + assert AddressView.address_page_title(address) == "Contract #{address}" + end + + test "uses the address hash when it is not a contract" do + address = build(:address, smart_contract: nil) + + assert AddressView.address_page_title(address) == "#{address}" + end + 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 new file mode 100644 index 0000000..cc1c9f1 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs @@ -0,0 +1,117 @@ +defmodule BlockScoutWeb.ApiDocsViewTest do + use BlockScoutWeb.ConnCase, async: false + + alias BlockScoutWeb.APIDocsView + + describe "api_url/1" do + setup do + 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 "adds slash before path" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", port: 9999, api_path: "/chain/dog"] + ) + + assert APIDocsView.api_url() == "https://blockscout.com/chain/dog/api" + end + + test "does not add slash to empty path" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", port: 9999, api_path: ""] + ) + + assert APIDocsView.api_url() == "https://blockscout.com/api" + end + + test "localhost return with port" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "http", host: "localhost"], + http: [port: 9999] + ) + + assert APIDocsView.api_url() == "http://localhost:9999/api" + end + end + + describe "blockscout_url/2" do + setup do + 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 + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/eth/mainnet"] + ) + + assert APIDocsView.blockscout_url(true, true) == "https://blockscout.com/eth/mainnet" + end + + test "set_path = false returns url w/out path" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/eth/mainnet"] + ) + + assert APIDocsView.blockscout_url(false) == "https://blockscout.com" + end + + test "set_path = true is_api returns url with api_path" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/"] + ) + + assert APIDocsView.blockscout_url(true, true) == "https://blockscout.com/eth/mainnet" + end + + test "set_path = true is_api returns url with path" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/eth/mainnet2"] + ) + + assert APIDocsView.blockscout_url(true, false) == "https://blockscout.com/eth/mainnet2" + end + end + + describe "eth_rpc_api_url/1" do + setup do + 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 "adds slash before path" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", port: 9999, api_path: "/chain/dog"] + ) + + assert APIDocsView.eth_rpc_api_url() == "https://blockscout.com/chain/dog/api/eth-rpc" + end + + test "does not add slash to empty path" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "https", host: "blockscout.com", port: 9999, path: ""] + ) + + assert APIDocsView.eth_rpc_api_url() == "https://blockscout.com/api/eth-rpc" + end + + test "localhost return with port" do + Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, + url: [scheme: "http", host: "localhost"], + http: [port: 9999] + ) + + assert APIDocsView.eth_rpc_api_url() == "http://localhost:9999/api/eth-rpc" + end + end +end 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 new file mode 100644 index 0000000..eea5baf --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs @@ -0,0 +1,98 @@ +defmodule BlockScoutWeb.BlockViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.BlockView + alias Explorer.Repo + + describe "average_gas_price/1" do + test "returns an average of the gas prices for a block's transactions with the unit value" do + block = insert(:block) + + Enum.each(1..10, fn index -> + :transaction + |> insert(gas_price: 10_000_000_000 * index) + |> with_block(block) + end) + + assert "55 Gwei" == BlockView.average_gas_price(Repo.preload(block, [:transactions])) + end + end + + describe "block_type/1" do + test "returns Block" do + block = insert(:block, nephews: []) + + assert BlockView.block_type(block) == "Block" + end + + test "returns Reorg" do + reorg = insert(:block, consensus: false, nephews: []) + + assert BlockView.block_type(reorg) == "Reorg" + end + + 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(preloaded) == "Uncle" + end + end + + describe "formatted_timestamp/1" do + test "returns a formatted timestamp string for a block" do + block = insert(:block) + + assert Timex.format!(block.timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) == + BlockView.formatted_timestamp(block) + end + end + + describe "show_reward?/1" do + test "returns false when list of rewards is empty" do + assert BlockView.show_reward?([]) == false + end + + test "returns true when list of rewards is not empty" do + block = insert(:block) + validator = insert(:reward, address_hash: block.miner_hash, block_hash: block.hash, address_type: :validator) + + assert BlockView.show_reward?([validator]) == true + end + end + + describe "combined_rewards_value/1" do + test "returns all the reward values summed up and formatted into a String" do + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator, + reward: Decimal.new(1_000_000_000_000_000_000) + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds, + reward: Decimal.new(1_000_000_000_000_000_000) + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :uncle, + reward: Decimal.new(1_000_042_000_000_000_000) + ) + + block = Repo.preload(block, :rewards) + + assert BlockView.combined_rewards_value(block) == "3.000042 ETH" + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs b/apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs new file mode 100644 index 0000000..172a06a --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs @@ -0,0 +1,84 @@ +defmodule BlockScoutWeb.CurrencyHelpersTest do + use ExUnit.Case + + alias BlockScoutWeb.CurrencyHelpers + + doctest BlockScoutWeb.CurrencyHelpers, import: true + + describe "format_according_to_decimals/1" do + test "formats the amount as value considering the given decimals" do + amount = Decimal.new(205_000_000_000_000) + decimals = Decimal.new(12) + + assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "205" + end + + test "considers the decimal places according to the given decimals" do + amount = Decimal.new(205_000) + decimals = Decimal.new(12) + + assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "0.000000205" + end + + test "does not consider right zeros in decimal places" do + amount = Decimal.new(90_000_000) + decimals = Decimal.new(6) + + assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "90" + end + + test "returns the full number when there is no right zeros in decimal places" do + amount = Decimal.new(9_324_876) + decimals = Decimal.new(6) + + assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "9.324876" + end + + test "formats the value considering thousands separators" do + amount = Decimal.new(1_000_450) + decimals = Decimal.new(2) + + assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "10,004.5" + end + + test "supports value as integer" do + amount = 1_000_450 + decimals = Decimal.new(2) + + assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "10,004.5" + end + + test "considers 0 when decimals is nil" do + amount = 1_000_450 + decimals = nil + + assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "1,000,450" + end + end + + describe "format_integer_to_currency/1" do + test "formats the integer value to a currency format" do + assert CurrencyHelpers.format_integer_to_currency(9000) == "9,000" + end + end + + describe "divide_decimals/2" do + test "divide by the given decimal amount" do + result = CurrencyHelpers.divide_decimals(Decimal.new(1000), Decimal.new(3)) + expected_result = Decimal.new(1) + assert Decimal.compare(result, expected_result) == :eq + end + + test "work when number of decimals is bigger than the number's digits" do + result = CurrencyHelpers.divide_decimals(Decimal.new(1000), Decimal.new(5)) + expected_result = Decimal.from_float(0.01) + assert Decimal.compare(result, expected_result) == :eq + end + + test "return the same number when number of decimals is 0" do + result = CurrencyHelpers.divide_decimals(Decimal.new(1000), Decimal.new(0)) + expected_result = Decimal.new(1000) + assert Decimal.compare(result, expected_result) == :eq + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/error_helpers_test.exs b/apps/block_scout_web/test/block_scout_web/views/error_helpers_test.exs new file mode 100644 index 0000000..ffa3899 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/error_helpers_test.exs @@ -0,0 +1,42 @@ +defmodule BlockScoutWeb.ErrorHelpersTest do + use BlockScoutWeb.ConnCase, async: true + import Phoenix.HTML.Tag, only: [content_tag: 3] + + alias BlockScoutWeb.ErrorHelpers + + @changeset %{ + errors: [ + contract_code: {"has already been taken", []} + ] + } + + describe "error_tag tests" do + test "error_tag/2 renders spans with default options" do + assert ErrorHelpers.error_tag(@changeset, :contract_code) == [ + content_tag(:span, "has already been taken", class: "has-error") + ] + end + + test "error_tag/3 overrides default options" do + assert ErrorHelpers.error_tag(@changeset, :contract_code, class: "something-else") == [ + content_tag(:span, "has already been taken", class: "something-else") + ] + end + + test "error_tag/3 merges given options with default ones" do + assert ErrorHelpers.error_tag(@changeset, :contract_code, data_hidden: true) == [ + content_tag(:span, "has already been taken", class: "has-error", data_hidden: true) + ] + end + end + + describe "translate_error/1 tests" do + test "returns errors" do + assert ErrorHelpers.translate_error({"test", []}) == "test" + end + + test "returns errors with count" do + assert ErrorHelpers.translate_error({"%{count} test", [count: 1]}) == "1 test" + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/error_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/error_view_test.exs new file mode 100644 index 0000000..6babf7c --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/error_view_test.exs @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.ErrorViewTest do + use BlockScoutWeb.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.html" do + assert render_to_string(BlockScoutWeb.ErrorView, "404.html", []) == "Page not found" + end + + test "renders 422.html" do + assert render_to_string(BlockScoutWeb.ErrorView, "422.html", []) == "Unprocessable entity" + end + + test "render 500.html" do + assert render_to_string(BlockScoutWeb.ErrorView, "500.html", []) == "Internal server error" + end + + test "render any other" do + assert render_to_string(BlockScoutWeb.ErrorView, "505.html", []) == "Internal server error" + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/internal_transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/internal_transaction_view_test.exs new file mode 100644 index 0000000..987f89f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/internal_transaction_view_test.exs @@ -0,0 +1,40 @@ +defmodule BlockScoutWeb.InternalTransactionViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.InternalTransactionView + alias Explorer.Chain.InternalTransaction + + doctest BlockScoutWeb.InternalTransactionView + + describe "type/1" do + test "returns the correct string when the type is :call and call type is :call" do + internal_transaction = %InternalTransaction{type: :call, call_type: :call} + + assert InternalTransactionView.type(internal_transaction) == "Call" + end + + test "returns the correct string when the type is :call and call type is :delegate_call" do + internal_transaction = %InternalTransaction{type: :call, call_type: :delegatecall} + + assert InternalTransactionView.type(internal_transaction) == "Delegate Call" + end + + test "returns the correct string when the type is :create" do + internal_transaction = %InternalTransaction{type: :create} + + assert InternalTransactionView.type(internal_transaction) == "Create" + end + + test "returns the correct string when the type is :selfdestruct" do + internal_transaction = %InternalTransaction{type: :selfdestruct} + + assert InternalTransactionView.type(internal_transaction) == "Self-Destruct" + end + + test "returns the correct string when the type is :reward" do + internal_transaction = %InternalTransaction{type: :reward} + + assert InternalTransactionView.type(internal_transaction) == "Reward" + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs new file mode 100644 index 0000000..b194934 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs @@ -0,0 +1,203 @@ +defmodule BlockScoutWeb.LayoutViewTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.LayoutView + + test "configured_social_media_services/0" do + assert length(LayoutView.configured_social_media_services()) > 0 + end + + setup do + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, []) + end) + end + + describe "logo/0" do + test "use the environment logo when it's configured" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, logo: "custom/logo.png") + + assert LayoutView.logo() == "custom/logo.png" + end + + test "logo is nil when there is no env configured for it" do + assert LayoutView.logo() == nil + end + end + + describe "subnetwork_title/0" do + test "use the environment subnetwork title when it's configured" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, subnetwork: "Subnetwork Test") + + assert LayoutView.subnetwork_title() == "Subnetwork Test" + end + + test "use the default subnetwork title when there is no env configured for it" do + assert LayoutView.subnetwork_title() == "Sokol" + end + end + + describe "network_title/0" do + test "use the environment network title when it's configured" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, network: "Custom Network") + + assert LayoutView.network_title() == "Custom Network" + end + + test "use the default network title when there is no env configured for it" do + assert LayoutView.network_title() == "POA" + end + end + + describe "release_link/1" do + test "set empty string if no blockscout version configured" do + Application.put_env(:block_scout_web, :blockscout_version, nil) + + assert LayoutView.release_link(nil) == "" + end + + test "set empty string if blockscout version is empty string" do + Application.put_env(:block_scout_web, :blockscout_version, "") + + assert LayoutView.release_link("") == "" + end + + test "use the default value when there is no release_link env configured for it" do + Application.put_env(:block_scout_web, :release_link, nil) + + assert LayoutView.release_link("v1.3.4-beta") == + {:safe, + ~s(v1.3.4-beta)} + end + + test "use the default value when empty release_link env configured for it" do + Application.put_env(:block_scout_web, :release_link, "") + + assert LayoutView.release_link("v1.3.4-beta") == + {:safe, + ~s(v1.3.4-beta)} + end + + test "use the environment release link when it's configured" do + Application.put_env( + :block_scout_web, + :release_link, + "https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" + ) + + assert LayoutView.release_link("v1.3.4-beta") == + {:safe, + ~s(v1.3.4-beta)} + end + end + + @supported_chains_pattern ~s([ { "title": "RSK", "url": "https://blockscout.com/rsk/mainnet", "other?": true }, { "title": "Sokol", "url": "https://blockscout.com/poa/sokol", "test_net?": true }, { "title": "POA", "url": "https://blockscout.com/poa/core" }, { "title": "LUKSO L14", "url": "https://blockscout.com/lukso/l14", "test_net?": true, "hide_in_dropdown?": true } ]) + + describe "other_networks/0" do + test "get networks list based on env variables" do + Application.put_env(:block_scout_web, :other_networks, @supported_chains_pattern) + + assert LayoutView.other_networks() == [ + %{ + title: "POA", + url: "https://blockscout.com/poa/core" + }, + %{ + title: "RSK", + url: "https://blockscout.com/rsk/mainnet", + other?: true + }, + %{ + title: "LUKSO L14", + url: "https://blockscout.com/lukso/l14", + test_net?: true, + hide_in_dropdown?: true + } + ] + end + + test "get empty networks list if SUPPORTED_CHAINS is not parsed" do + Application.put_env(:block_scout_web, :other_networks, "not a valid json") + + assert LayoutView.other_networks() == [] + end + end + + describe "main_nets/1" do + test "get all main networks list based on env variables" do + Application.put_env(:block_scout_web, :other_networks, @supported_chains_pattern) + + assert LayoutView.main_nets(LayoutView.other_networks()) == [ + %{ + title: "POA", + url: "https://blockscout.com/poa/core" + }, + %{ + title: "RSK", + url: "https://blockscout.com/rsk/mainnet", + other?: true + } + ] + end + end + + describe "test_nets/1" do + test "get all networks list based on env variables" do + Application.put_env(:block_scout_web, :other_networks, @supported_chains_pattern) + + assert LayoutView.test_nets(LayoutView.other_networks()) == [ + %{ + title: "LUKSO L14", + url: "https://blockscout.com/lukso/l14", + test_net?: true, + hide_in_dropdown?: true + } + ] + end + end + + describe "dropdown_nets/0" do + test "get all dropdown networks list based on env variables" do + Application.put_env(:block_scout_web, :other_networks, @supported_chains_pattern) + + assert LayoutView.dropdown_nets() == [ + %{ + title: "POA", + url: "https://blockscout.com/poa/core" + }, + %{ + title: "RSK", + url: "https://blockscout.com/rsk/mainnet", + other?: true + } + ] + end + end + + describe "dropdown_head_main_nets/0" do + test "get dropdown all main networks except those of 'other' type list based on env variables" do + Application.put_env(:block_scout_web, :other_networks, @supported_chains_pattern) + + assert LayoutView.dropdown_head_main_nets() == [ + %{ + title: "POA", + url: "https://blockscout.com/poa/core" + } + ] + end + end + + describe "dropdown_other_nets/0" do + test "get dropdown networks of 'other' type list based on env variables" do + Application.put_env(:block_scout_web, :other_networks, @supported_chains_pattern) + + assert LayoutView.dropdown_other_nets() == [ + %{ + title: "RSK", + url: "https://blockscout.com/rsk/mainnet", + other?: true + } + ] + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/render_helpers_test.exs b/apps/block_scout_web/test/block_scout_web/views/render_helpers_test.exs new file mode 100644 index 0000000..5cc0d1f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/render_helpers_test.exs @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.RenderHelpersTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.{BlockView, RenderHelpers} + + describe "render_partial/1" do + test "renders text" do + assert "test" == RenderHelpers.render_partial("test") + end + + test "renders the proper partial when view_module, partial and args are given" do + block = build(:block) + + assert {:safe, _} = + RenderHelpers.render_partial( + view_module: BlockView, + partial: "_link.html", + block: block + ) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/search_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/search_view_test.exs new file mode 100644 index 0000000..8bc64ad --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/search_view_test.exs @@ -0,0 +1,44 @@ +defmodule BlockScoutWeb.SearchViewTest do + use ExUnit.Case + alias BlockScoutWeb.SearchView + + test "highlight_search_result/2 returns search result if query doesn't match" do + query = "test" + search_result = "qwerty" + res = SearchView.highlight_search_result(search_result, query) + IO.inspect(res) + + assert res == {:safe, search_result} + end + + test "highlight_search_result/2 returns safe HTML of unsafe search result if query doesn't match" do + query = "test" + search_result = "qwe1'\">