Commit e5bf636e authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into tom2drum/issue-1355

parents 9c91fd25 596ad55c
...@@ -5,6 +5,9 @@ const RESTRICTED_MODULES = { ...@@ -5,6 +5,9 @@ const RESTRICTED_MODULES = {
{ name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' }, { name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' },
{ name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' }, { name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' },
], ],
patterns: [
'icons/*',
],
}; };
module.exports = { module.exports = {
......
...@@ -22,7 +22,7 @@ jobs: ...@@ -22,7 +22,7 @@ jobs:
code_quality: code_quality:
name: Code quality name: Code quality
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ !contains(github.event.pull_request.labels.*.name, 'WIP') && !(github.event.action == 'unlabeled' && github.event.label.name != 'WIP') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip checks') && !(github.event.action == 'unlabeled' && github.event.label.name != 'skip checks') }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
......
...@@ -29,5 +29,5 @@ jobs: ...@@ -29,5 +29,5 @@ jobs:
cleanup_docker_image: cleanup_docker_image:
uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup_docker.yaml@master uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup_docker.yaml@master
with: with:
dockerImage: prerelease-$GITHUB_REF_NAME_SLUG dockerImage: review-$GITHUB_REF_NAME_SLUG
secrets: inherit secrets: inherit
...@@ -4,7 +4,7 @@ on: ...@@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
push: push:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+-[a-z]+*' # e.g v1.2.3-alpha - 'v[0-9]+.[0-9]+.[0-9]+-[a-z]+*' # e.g v1.2.3-alpha.2
jobs: jobs:
checks: checks:
...@@ -24,9 +24,31 @@ jobs: ...@@ -24,9 +24,31 @@ jobs:
uses: "./.github/workflows/e2e-tests.yml" uses: "./.github/workflows/e2e-tests.yml"
secrets: inherit secrets: inherit
version:
name: Pre-release version info
runs-on: ubuntu-latest
outputs:
is_initial: ${{ steps.is_initial.outputs.result }}
steps:
- name: Determine if it is the initial version of the pre-release
id: is_initial
uses: actions/github-script@v6
env:
TAG: ${{ github.ref_name }}
with:
script: |
const tag = process.env.TAG;
const REGEXP = /^v[0-9]+.[0-9]+.[0-9]+-[a-z]+((\.|-)\d+)?$/i;
const match = tag.match(REGEXP);
const isInitial = match && !match[1] ? true : false;
core.info('is_initial flag value: ', isInitial);
return isInitial;
label_issues: label_issues:
name: Add pre-release label to issues name: Add pre-release label to issues
uses: './.github/workflows/label-issues-in-release.yml' uses: './.github/workflows/label-issues-in-release.yml'
needs: [ version ]
if: ${{ needs.version.outputs.is_initial == 'true' }}
with: with:
tag: ${{ github.ref_name }} tag: ${{ github.ref_name }}
label_name: 'pre-release' label_name: 'pre-release'
......
...@@ -45,4 +45,4 @@ jobs: ...@@ -45,4 +45,4 @@ jobs:
run: yarn sentry-cli sourcemaps inject ./.next run: yarn sentry-cli sourcemaps inject ./.next
- name: Upload source maps to Sentry - name: Upload source maps to Sentry
run: yarn sentry-cli sourcemaps upload --release=${{ github.ref_name }} --validate ./.next run: yarn sentry-cli sourcemaps upload --release=${{ github.ref_name }} --url-prefix=~/_next/ --validate ./.next
\ No newline at end of file \ No newline at end of file
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
/out/ /out/
/public/assets/ /public/assets/
/public/envs.js /public/envs.js
/public/icons/sprite.svg
/public/icons/README.md
/analyze /analyze
# production # production
......
...@@ -265,7 +265,7 @@ ...@@ -265,7 +265,7 @@
}, },
{ {
"type": "npm", "type": "npm",
"script": "format-svg", "script": "svg:format",
"problemMatcher": [], "problemMatcher": [],
"label": "format svg", "label": "format svg",
"detail": "format svg files with svgo", "detail": "format svg files with svgo",
...@@ -318,6 +318,7 @@ ...@@ -318,6 +318,7 @@
"main.L2", "main.L2",
"poa_core", "poa_core",
"eth_goerli", "eth_goerli",
"sepolia",
"eth", "eth",
"rootstock", "rootstock",
"polygon", "polygon",
......
...@@ -58,6 +58,7 @@ RUN ./collect_envs.sh ./docs/ENVS.md ...@@ -58,6 +58,7 @@ RUN ./collect_envs.sh ./docs/ENVS.md
# Build app for production # Build app for production
RUN yarn build RUN yarn build
RUN yarn svg:build-sprite
### FEATURE REPORTER ### FEATURE REPORTER
...@@ -89,6 +90,10 @@ WORKDIR /app ...@@ -89,6 +90,10 @@ WORKDIR /app
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
COPY --from=builder /app/next.config.js ./ COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/package.json ./package.json
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
App is distributed as a docker image. Here you can find information about the [package](https://github.com/blockscout/frontend/pkgs/container/frontend) and its recent [releases](https://github.com/blockscout/frontend/releases). App is distributed as a docker image. Here you can find information about the [package](https://github.com/blockscout/frontend/pkgs/container/frontend) and its recent [releases](https://github.com/blockscout/frontend/releases).
You can configure your app by passing necessary environment variables when stating the container. See full list of ENVs and their description [here](./docs/ENVS.md). You can configure your app by passing necessary environment variables when starting the container. See full list of ENVs and their description [here](./docs/ENVS.md).
```sh ```sh
docker run -p 3000:3000 --env-file <path-to-your-env-file> ghcr.io/blockscout/frontend:latest docker run -p 3000:3000 --env-file <path-to-your-env-file> ghcr.io/blockscout/frontend:latest
...@@ -29,6 +29,7 @@ See our [Contribution guide](./docs/CONTRIBUTING.md) for pull request protocol. ...@@ -29,6 +29,7 @@ See our [Contribution guide](./docs/CONTRIBUTING.md) for pull request protocol.
- [Contribution guide](./docs/CONTRIBUTING.md) - [Contribution guide](./docs/CONTRIBUTING.md)
- [Making a custom build](./docs/CUSTOM_BUILD.md) - [Making a custom build](./docs/CUSTOM_BUILD.md)
- [Frontend migration guide](https://docs.blockscout.com/for-developers/frontend-migration) - [Frontend migration guide](https://docs.blockscout.com/for-developers/frontend-migration)
- [Manual deployment guide with backend and microservices](https://docs.blockscout.com/for-developers/deployment/manual-deployment-guide)
## License ## License
......
...@@ -17,6 +17,7 @@ export { default as sentry } from './sentry'; ...@@ -17,6 +17,7 @@ export { default as sentry } from './sentry';
export { default as sol2uml } from './sol2uml'; export { default as sol2uml } from './sol2uml';
export { default as stats } from './stats'; export { default as stats } from './stats';
export { default as suave } from './suave'; export { default as suave } from './suave';
export { default as txInterpretation } from './txInterpretation';
export { default as web3Wallet } from './web3Wallet'; export { default as web3Wallet } from './web3Wallet';
export { default as verifiedTokens } from './verifiedTokens'; export { default as verifiedTokens } from './verifiedTokens';
export { default as zkEvmRollup } from './zkEvmRollup'; export { default as zkEvmRollup } from './zkEvmRollup';
import type { Feature } from './types';
import type { Provider } from 'types/client/txInterpretation';
import { PROVIDERS } from 'types/client/txInterpretation';
import { getEnvValue } from '../utils';
const title = 'Transaction interpretation';
const provider: Provider = (() => {
const value = getEnvValue('NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER');
if (value && PROVIDERS.includes(value as Provider)) {
return value as Provider;
}
return 'none';
})();
const config: Feature<{ provider: Provider }> = (() => {
if (provider !== 'none') {
return Object.freeze({
title,
provider,
isEnabled: true,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
...@@ -27,6 +27,7 @@ const hiddenViews = (() => { ...@@ -27,6 +27,7 @@ const hiddenViews = (() => {
const config = Object.freeze({ const config = Object.freeze({
identiconType, identiconType,
hiddenViews, hiddenViews,
solidityscanEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED') === 'true',
}); });
export default config; export default config;
...@@ -42,6 +42,7 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com ...@@ -42,6 +42,7 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
#meta #meta
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true
...@@ -47,6 +47,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com ...@@ -47,6 +47,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask'] NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask']
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED='true'
#meta #meta
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true
# Set of ENVs for Sepolia testnet network explorer
# https://eth-sepolia.blockscout.com/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME=Sepolia
NEXT_PUBLIC_NETWORK_SHORT_NAME=Sepolia
NEXT_PUBLIC_NETWORK_ID=11155111
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://eth-sepolia.public.blastapi.io
NEXT_PUBLIC_IS_TESTNET=true
# api configuration
NEXT_PUBLIC_API_HOST=eth-sepolia.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgba(51, 53, 67, 1)'
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(165, 252, 122, 1)'
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}]
## footer
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/sepolia.json
##views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://sepolia.looksrare.org/collections/{hash}','instance_url':'https://sepolia.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://sepolia.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}},{'title':'Tenderly','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/sepolia'}}]
# app features
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscout-goerli.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask']
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_HAS_BEACON_CHAIN=true
#meta
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png
...@@ -15,6 +15,7 @@ import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; ...@@ -15,6 +15,7 @@ import type { MarketplaceAppOverview } from '../../../types/client/marketplace';
import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items';
import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items';
import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token';
import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation';
import type { WalletType } from '../../../types/client/wallets'; import type { WalletType } from '../../../types/client/wallets';
import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks';
...@@ -392,6 +393,7 @@ const schema = yup ...@@ -392,6 +393,7 @@ const schema = yup
.transform(replaceQuotes) .transform(replaceQuotes)
.json() .json()
.of(yup.string<AddressViewId>().oneOf(ADDRESS_VIEWS_IDS)), .of(yup.string<AddressViewId>().oneOf(ADDRESS_VIEWS_IDS)),
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: yup.boolean(),
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: yup NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: yup
.array() .array()
.transform(replaceQuotes) .transform(replaceQuotes)
...@@ -437,6 +439,7 @@ const schema = yup ...@@ -437,6 +439,7 @@ const schema = yup
return isNoneSchema.isValidSync(data) || isArrayOfWalletsSchema.isValidSync(data); return isNoneSchema.isValidSync(data) || isArrayOfWalletsSchema.isValidSync(data);
}), }),
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: yup.boolean(), NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: yup.boolean(),
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: yup.string().oneOf(TX_INTERPRETATION_PROVIDERS),
NEXT_PUBLIC_AD_TEXT_PROVIDER: yup.string<AdTextProviders>().oneOf(SUPPORTED_AD_TEXT_PROVIDERS), NEXT_PUBLIC_AD_TEXT_PROVIDER: yup.string<AdTextProviders>().oneOf(SUPPORTED_AD_TEXT_PROVIDERS),
NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE: yup.boolean(), NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE: yup.boolean(),
NEXT_PUBLIC_OG_DESCRIPTION: yup.string(), NEXT_PUBLIC_OG_DESCRIPTION: yup.string(),
......
...@@ -51,8 +51,8 @@ frontend: ...@@ -51,8 +51,8 @@ frontend:
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg
NEXT_PUBLIC_API_HOST: etc.blockscout.com NEXT_PUBLIC_API_HOST: eth-goerli.blockscout.com
NEXT_PUBLIC_STATS_API_HOST: https://stats-etc.k8s.blockscout.com/ NEXT_PUBLIC_STATS_API_HOST: https://stats-goerli.k8s-dev.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST: http://visualizer-svc.visualizer-testing.svc.cluster.local/ NEXT_PUBLIC_VISUALIZE_API_HOST: http://visualizer-svc.visualizer-testing.svc.cluster.local/
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com
...@@ -67,6 +67,7 @@ frontend: ...@@ -67,6 +67,7 @@ frontend:
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: "['top_accounts']" NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: "['top_accounts']"
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: true
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: "['value','fee_currency','gas_price','gas_fees','burnt_fees']" NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: "['value','fee_currency','gas_price','gas_fees','burnt_fees']"
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']" NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']"
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
......
...@@ -33,7 +33,7 @@ We are using following technology stack in the project ...@@ -33,7 +33,7 @@ We are using following technology stack in the project
- [Yarn](https://yarnpkg.com/) as package manager - [Yarn](https://yarnpkg.com/) as package manager
- [ReactJS](https://reactjs.org/) as UI library - [ReactJS](https://reactjs.org/) as UI library
- [Next.js](https://nextjs.org/) as application framework - [Next.js](https://nextjs.org/) as application framework
- [Chakra](https://chakra-ui.com/) as component library; our theme customization could be found in `/theme` folder - [Chakra](https://chakra-ui.com/) as component library; our theme customization can be found in `/theme` folder
- [TanStack Query](https://tanstack.com/query/v4/docs/react/overview/) for fetching, caching and updating data from the API - [TanStack Query](https://tanstack.com/query/v4/docs/react/overview/) for fetching, caching and updating data from the API
- [Jest](https://jestjs.io/) as JavaScript testing framework - [Jest](https://jestjs.io/) as JavaScript testing framework
- [Playwright](https://playwright.dev/) as a tool for components visual testing - [Playwright](https://playwright.dev/) as a tool for components visual testing
...@@ -44,15 +44,22 @@ And of course our premier language is [Typescript](https://www.typescriptlang.or ...@@ -44,15 +44,22 @@ And of course our premier language is [Typescript](https://www.typescriptlang.or
## Local development ## Local development
1. Prepare your environment variables: To develop locally, follow one of the two paths outlined below:
- clone `.env.example` into `configs/envs/.env.secrets` and fill it with necessary secrets for the [external services](./ENVS.md#external-services-configuration) integration; you can pick up only those that your needed
- choose one of the following options: A. Custom configuration:
A. create `.env.local` file in the root folder with environment variables from the [list](./ENVS.md); all required variables should be present in the file;
B. pick up one of the predefined configurations located at `/configs/envs` folder; no actual action is needed at this stage; 1. Create `.env.local` file in the root folder and include all required environment variables from the [list](./ENVS.md)
2. Run your local dev server: 2. Optionally, clone `.env.example` and name it `.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets your need.
- if you picked up option "A" above, use `yarn dev` command 3. Use `yarn dev` command to start the dev server.
- if your options is "B", use `yarn dev:<config_name>` command 4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`).
3. In browser navigate to the URL from the command output (by default, it is `http://localhost:3000`)
B. Pre-defined configuration:
1. Optionally, clone `.env.example` file into `configs/envs/.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets your need.
2. Choose one of the predefined configurations located in the `/configs/envs` folder.
3. Start your local dev server using the `yarn dev:<config_name>` command.
4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`).
&nbsp; &nbsp;
...@@ -150,11 +157,11 @@ We have 3 pre-configured projects. You can run your test with the desired projec ...@@ -150,11 +157,11 @@ We have 3 pre-configured projects. You can run your test with the desired projec
### Opening PR and getting it accepted ### Opening PR and getting it accepted
1. Push your changes and create a Pull Request. If you are still working on the task, please use "Draft Pull Request" option, so we know that it is not ready yet. In addition, you can add label "WIP" to your PR, so all CI checks will not be triggered. 1. Push your changes and create a Pull Request. If you are still working on the task, please use "Draft Pull Request" option, so we know that it is not ready yet. In addition, you can add label "skip checks" to your PR, so all CI checks will not be triggered.
2. Once you finish your work, remove label "WIP" from PR, if it was added before, and publish PR if it was in the draft state 2. Once you finish your work, remove label "skip checks" from PR, if it was added before, and publish PR if it was in the draft state
3. Make sure that all code checks and tests are successfully passed 3. Make sure that all code checks and tests are successfully passed
4. Add description to your Pull Request and link an existing issue(s) that it is fixing 4. Add description to your Pull Request and link an existing issue(s) that it is fixing
5. Request review from one or all core team members: @tom2drum, @isstuev. Our core team are committed to reviewing patches in a timely manner. 5. Request review from one or all core team members: @tom2drum, @isstuev. Our core team is committed to reviewing patches in a timely manner.
6. After code review is done, we merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface. 6. After code review is done, we merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface.
*Note*, if you Pull Request contains any changes that are not backwards compatible with the previous versions of the app, please specify them in PR description and add label ["breaking changes"](https://github.com/blockscout/frontend/labels/breaking%20changes) to it. *Note*, if you Pull Request contains any changes that are not backwards compatible with the previous versions of the app, please specify them in PR description and add label ["breaking changes"](https://github.com/blockscout/frontend/labels/breaking%20changes) to it.
...@@ -175,7 +182,8 @@ We have 3 pre-configured projects. You can run your test with the desired projec ...@@ -175,7 +182,8 @@ We have 3 pre-configured projects. You can run your test with the desired projec
| `yarn lint:eslint` | lint project files with ESLint | | `yarn lint:eslint` | lint project files with ESLint |
| `yarn lint:eslint:fix` | lint project files with ESLint and automatically fix problems | | `yarn lint:eslint:fix` | lint project files with ESLint and automatically fix problems |
| `yarn lint:tsc` | compile project typescript files using TypeScript Compiler | | `yarn lint:tsc` | compile project typescript files using TypeScript Compiler |
| `yarn format-svg` | format and optimize SVG icons in the `/icons` folder using SVGO tool | | `yarn svg:format` | format and optimize SVG icons in the `/icons` folder using SVGO tool |
| `yarn svg:build-sprite` | build SVG icons sprite |
| **Testing** | | **Testing** |
| `yarn test:jest` | run all Jest unit tests | | `yarn test:jest` | run all Jest unit tests |
| `yarn test:jest:watch` | run all Jest unit tests in watch mode | | `yarn test:jest:watch` | run all Jest unit tests in watch mode |
...@@ -195,4 +203,4 @@ There are some predefined tasks for all commands described above. You can see it ...@@ -195,4 +203,4 @@ There are some predefined tasks for all commands described above. You can see it
Also there is a Jest test launch configuration for debugging and running current test file in the watch mode. Also there is a Jest test launch configuration for debugging and running current test file in the watch mode.
And you may find the Dev Container setup useful too. And you may find the Dev Container setup useful too.
\ No newline at end of file
...@@ -7,4 +7,4 @@ For running app container from freshly built image do ...@@ -7,4 +7,4 @@ For running app container from freshly built image do
docker run -p 3000:3000 --env-file <path-to-your-env-file> <your-image-tag> docker run -p 3000:3000 --env-file <path-to-your-env-file> <your-image-tag>
``` ```
*Disclaimer* Do no try to generate production build of the app on your local machine (outside the docker). The app will not work as you would expect. *Disclaimer* Do not try to generate production build of the app on your local machine (outside the docker). The app will not work as you would expect.
...@@ -44,6 +44,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -44,6 +44,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [Solidity to UML diagrams](ENVS.md#solidity-to-uml-diagrams) - [Solidity to UML diagrams](ENVS.md#solidity-to-uml-diagrams)
- [Blockchain statistics](ENVS.md#blockchain-statistics) - [Blockchain statistics](ENVS.md#blockchain-statistics)
- [Web3 wallet integration](ENVS.md#web3-wallet-integration-add-token-or-network-to-the-wallet) (add token or network to the wallet) - [Web3 wallet integration](ENVS.md#web3-wallet-integration-add-token-or-network-to-the-wallet) (add token or network to the wallet)
- [Transaction interpretation](ENVS.md#transaction-interpretation)
- [Verified tokens info](ENVS.md#verified-tokens-info) - [Verified tokens info](ENVS.md#verified-tokens-info)
- [Bridged tokens](ENVS.md#bridged-tokens) - [Bridged tokens](ENVS.md#bridged-tokens)
- [Safe{Core} address tags](ENVS.md#safecore-address-tags) - [Safe{Core} address tags](ENVS.md#safecore-address-tags)
...@@ -128,7 +129,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -128,7 +129,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| url | `string` | Network explorer main page url | Required | - | `https://blockscout.com/xdai/mainnet` | | url | `string` | Network explorer main page url | Required | - | `https://blockscout.com/xdai/mainnet` |
| group | `Mainnets \| Testnets \| Other` | Indicates in which tab network appears in the menu | Required | - | `Mainnets` | | group | `Mainnets \| Testnets \| Other` | Indicates in which tab network appears in the menu | Required | - | `Mainnets` |
| icon | `string` | Network icon; if not provided, the common placeholder will be shown; *Note* that icon size should be at least 60px by 60px | - | - | `https://placekitten.com/60/60` | | icon | `string` | Network icon; if not provided, the common placeholder will be shown; *Note* that icon size should be at least 60px by 60px | - | - | `https://placekitten.com/60/60` |
| isActive | `boolean` | Pass `true` if item should be shonw as active in the menu | - | - | `true` | | isActive | `boolean` | Pass `true` if item should be shown as active in the menu | - | - | `true` |
| invertIconInDarkMode | `boolean` | Pass `true` if icon colors should be inverted in dark mode | - | - | `true` | | invertIconInDarkMode | `boolean` | Pass `true` if icon colors should be inverted in dark mode | - | - | `true` |
&nbsp; &nbsp;
...@@ -188,6 +189,7 @@ Settings for meta tags and OG tags ...@@ -188,6 +189,7 @@ Settings for meta tags and OG tags
| `burnt_fees` | Burnt fees | | `burnt_fees` | Burnt fees |
| `total_reward` | Total block reward | | `total_reward` | Total block reward |
| `nonce` | Block nonce | | `nonce` | Block nonce |
| `miner` | Address of block's miner or validator |
&nbsp; &nbsp;
...@@ -197,6 +199,7 @@ Settings for meta tags and OG tags ...@@ -197,6 +199,7 @@ Settings for meta tags and OG tags
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` | | NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` |
| NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS | `Array<AddressViewId>` | Address views that should not be displayed. See below the list of the possible id values. | - | - | `'["top_accounts"]'` | | NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS | `Array<AddressViewId>` | Address views that should not be displayed. See below the list of the possible id values. | - | - | `'["top_accounts"]'` |
| NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED | `boolean` | Set to `true` if SolidityScan reports are supported | - | - | `true` |
##### Address views list ##### Address views list
| Id | Description | | Id | Description |
...@@ -271,7 +274,7 @@ Settings for meta tags and OG tags ...@@ -271,7 +274,7 @@ Settings for meta tags and OG tags
## App features ## App features
*Note* The variables which are marked as required should be passed as described in order to enable the particular feature, but there are not required in the whole app context. *Note* The variables which are marked as required should be passed as described in order to enable the particular feature, but they are not required in the whole app context.
### My account ### My account
...@@ -286,7 +289,7 @@ Settings for meta tags and OG tags ...@@ -286,7 +289,7 @@ Settings for meta tags and OG tags
### Address verification in "My account" ### Address verification in "My account"
*Note* all ENV variables required for [My account](ENVS.md#my-account) feature should be passed along side with the following ones: *Note* all ENV variables required for [My account](ENVS.md#my-account) feature should be passed alongside the following ones:
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
...@@ -418,7 +421,7 @@ This feature is **always enabled**, but you can configure its behavior by passin ...@@ -418,7 +421,7 @@ This feature is **always enabled**, but you can configure its behavior by passin
| title | `string` | Displayed title of the app. | Required | `'The App'` | | title | `string` | Displayed title of the app. | Required | `'The App'` |
| logo | `string` | URL to logo file. Should be at least 288x288. | Required | `'https://foo.app/icon.png'` | | logo | `string` | URL to logo file. Should be at least 288x288. | Required | `'https://foo.app/icon.png'` |
| shortDescription | `string` | Displayed only in the app list. | Required | `'Awesome app'` | | shortDescription | `string` | Displayed only in the app list. | Required | `'Awesome app'` |
| categories | `Array<MarketplaceCategoryId>` | Displayed category. Select one of the following bellow. | Required | `['security', 'tools']` | | categories | `Array<MarketplaceCategoryId>` | Displayed category. Select one of the following below. | Required | `['security', 'tools']` |
| author | `string` | Displayed author of the app | Required | `'Bob'` | | author | `string` | Displayed author of the app | Required | `'Bob'` |
| url | `string` | URL of the app which will be launched in the iframe. | Required | `'https://foo.app/launch'` | | url | `string` | URL of the app which will be launched in the iframe. | Required | `'https://foo.app/launch'` |
| description | `string` | Displayed only in the modal dialog with additional info about the app. | Required | `'The best app'` | | description | `string` | Displayed only in the modal dialog with additional info about the app. | Required | `'The best app'` |
...@@ -471,6 +474,14 @@ This feature is **enabled by default** with the `['metamask']` value. To switch ...@@ -471,6 +474,14 @@ This feature is **enabled by default** with the `['metamask']` value. To switch
&nbsp; &nbsp;
### Transaction interpretation
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER | `blockscout` \| `none` | Transaction interpretation provider that displays human readable transaction description | - | `none` | `blockscout` |
&nbsp;
### Verified tokens info ### Verified tokens info
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
...@@ -511,13 +522,13 @@ This feature allows users to view tokens that have been bridged from other EVM c ...@@ -511,13 +522,13 @@ This feature allows users to view tokens that have been bridged from other EVM c
### Safe{Core} address tags ### Safe{Core} address tags
For the smart contract addresses which are [Safe{Core} accounts](https://safe.global/) public tag "Multisig: Safe" will be displayed in the address page header along side to Safe logo. The Safe service is available only for certain networks, see full list [here](https://docs.safe.global/safe-core-api/available-services). Based on provided value of `NEXT_PUBLIC_NETWORK_ID`, the feature will be enabled or disabled. For the smart contract addresses which are [Safe{Core} accounts](https://safe.global/) public tag "Multisig: Safe" will be displayed in the address page header alongside to Safe logo. The Safe service is available only for certain networks, see full list [here](https://docs.safe.global/safe-core-api/available-services). Based on provided value of `NEXT_PUBLIC_NETWORK_ID`, the feature will be enabled or disabled.
&nbsp; &nbsp;
### SUAVE chain ### SUAVE chain
For blockchains that implementing SUAVE architecture additional fields will be shown on the transaction page ("Allowed peekers", "Kettle"). Users also will be able to see the list of all transaction for a particular Kettle in the separate view. For blockchains that implement SUAVE architecture additional fields will be shown on the transaction page ("Allowed peekers", "Kettle"). Users also will be able to see the list of all transactions for a particular Kettle in the separate view.
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
......
## Description and Related Issue(s)
*[Provide a brief description of the changes or enhancements introduced by this pull request and explain motivation behind them. Cite any related issue(s) or bug(s) that it addresses using the [format](https://blog.github.com/2013-05-14-closing-issues-via-pull-requests/) `Fixes #123` or `Resolves #456`.]*
### Proposed Changes
*[Specify the changes or additions made in this pull request. Please mention if any changes were made to the ENV variables]*
### Breaking or Incompatible Changes
*[Describe any breaking or incompatible changes introduced by this pull request. Specify how users might need to modify their code or configurations to accommodate these changes.]*
### Additional Information
*[Include any additional information, context, or screenshots that may be helpful for reviewers.]*
## Checklist for PR author
- [ ] I have tested these changes locally.
- [ ] I added tests to cover any new functionality, following this [guide](./CONTRIBUTING.md#writing--running-tests)
- [ ] Whenever I fix a bug, I include a regression test to ensure that the bug does not reappear silently.
- [ ] If I have added, changed, renamed, or removed an environment variable, I have updated the list of environment variables in the [documentation](ENVS.md) and made the necessary changes to the validator script according to the [guide](./CONTRIBUTING.md#adding-new-env-variable)
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path fill="currentColor" fill-opacity=".8" fill-rule="evenodd" d="M9.767 2.074a.7.7 0 0 1 .626 0l7.38 3.69a.7.7 0 0 1 0 1.252l-7.38 3.69a.7.7 0 0 1-.626 0l-7.38-3.69a.7.7 0 0 1 0-1.252l7.38-3.69ZM4.266 6.39l5.814 2.907 5.815-2.907-5.815-2.907L4.266 6.39Zm-2.192 7.067a.7.7 0 0 1 .94-.313l7.066 3.534 7.067-3.534a.7.7 0 0 1 .627 1.252l-7.38 3.69a.7.7 0 0 1-.627 0l-7.38-3.69a.7.7 0 0 1-.313-.939Zm.94-4.003a.7.7 0 0 0-.627 1.252l7.38 3.69a.7.7 0 0 0 .626 0l7.38-3.69a.7.7 0 1 0-.626-1.252l-7.067 3.534-7.067-3.534Z" clip-rule="evenodd"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path fill="currentColor" d="M10.833 7.5H17.5L9.167 20v-7.5H3.333l7.5-12.5v7.5ZM9.167 9.167v-3.15l-2.89 4.816h4.556v3.662l3.553-5.328h-5.22Z"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.557 9.542a.75.75 0 0 1 .441.729 6.798 6.798 0 1 1-7.214-7.186.75.75 0 0 1 .548 1.307l-.149.133a3.786 3.786 0 0 0 5.364 5.344l.172-.173a.75.75 0 0 1 .838-.154ZM14.063 12a5.297 5.297 0 0 1-2.195.476 5.285 5.285 0 0 1-4.822-7.44A5.297 5.297 0 1 0 14.063 12Z" fill="currentColor"/>
<path d="m12.631 5.626.193.386a.75.75 0 0 0 .336.335l.386.194-.386.193a.75.75 0 0 0-.336.335l-.193.386-.193-.386a.75.75 0 0 0-.335-.335l-.386-.193.386-.194a.75.75 0 0 0 .335-.335l.193-.386Z" stroke="currentColor"/>
</svg>
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.557 7.712a.75.75 0 0 1 .442.729A7.983 7.983 0 1 1 7.526 0a.75.75 0 0 1 .548 1.308l-.18.161a4.675 4.675 0 0 0 6.619 6.603l.207-.207a.75.75 0 0 1 .837-.154Zm-1.446 2.504a6.176 6.176 0 0 1-8.373-8.311 6.481 6.481 0 0 0-2.282 10.657 6.482 6.482 0 0 0 10.655-2.346Z" fill="currentColor"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M15.557 9.542a.75.75 0 0 1 .441.729 6.798 6.798 0 1 1-7.214-7.186.75.75 0 0 1 .548 1.307l-.149.133a3.786 3.786 0 0 0 5.364 5.344l.172-.173a.75.75 0 0 1 .838-.154ZM14.063 12a5.297 5.297 0 0 1-2.195.476 5.285 5.285 0 0 1-4.822-7.44A5.297 5.297 0 1 0 14.063 12Z" fill="currentColor"/>
</svg> </svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.593A8.407 8.407 0 0 0 3.332 15.12c1.465-2.162 3.89-3.556 6.668-3.556 2.784 0 5.2 1.461 6.66 3.567A8.407 8.407 0 0 0 10 1.593ZM0 10C0 4.477 4.477 0 10 0s10 4.477 10 10-4.477 10-10 10S0 15.523 0 10Zm4.476 6.287c1.443 1.295 3.412 2.039 5.524 2.039a8.529 8.529 0 0 0 5.51-2.045c-1.17-1.85-3.2-3.123-5.51-3.123-2.333 0-4.363 1.225-5.524 3.129Zm3.388-8.975a2.136 2.136 0 1 1 4.272 0 2.136 2.136 0 0 1-4.272 0ZM10 3.584a3.729 3.729 0 1 0 0 7.457 3.729 3.729 0 0 0 0-7.457Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 3.6v4.812c0 6.447 5.414 8.703 6.79 9.17 1.377-.467 6.791-2.723 6.791-9.17V3.6H3ZM1.506 2.091A1.714 1.714 0 0 1 2.708 1.6h14.165c.448 0 .88.175 1.202.491.322.317.506.75.506 1.206v5.115c0 8.015-6.912 10.66-8.246 11.097a1.647 1.647 0 0 1-1.089 0C7.912 19.072 1 16.427 1 8.412V3.297c0-.455.184-.889.506-1.206Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.9 5.4a1.1 1.1 0 0 0-1.1 1.1v3.8a1.1 1.1 0 0 0 2.2 0V6.5a1.1 1.1 0 0 0-1.1-1.1Zm1.1 8.343a1.1 1.1 0 1 1-2.2 0 1.1 1.1 0 0 1 2.2 0Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 8.412V3.6h13.581v4.812c0 6.447-5.414 8.703-6.79 9.17C8.414 17.115 3 14.86 3 8.412ZM2.708 1.6c-.448 0-.88.175-1.202.491-.322.317-.506.75-.506 1.206v5.115c0 8.015 6.912 10.66 8.246 11.097.352.123.737.123 1.089 0 1.334-.437 8.246-3.082 8.246-11.097V3.297c0-.455-.184-.889-.506-1.206a1.714 1.714 0 0 0-1.202-.491H2.708ZM14.37 8.208a1 1 0 1 0-1.369-1.458L8.49 10.986 6.58 9.191a1 1 0 1 0-1.37 1.457l2.594 2.44a1 1 0 0 0 1.37 0l5.196-4.88Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m4.7 14.167-.592.591a.833.833 0 0 0 0 1.175.833.833 0 0 0 1.175 0l.592-.591A.833.833 0 0 0 4.7 14.167ZM4.166 10a.833.833 0 0 0-.833-.833H2.5a.833.833 0 1 0 0 1.666h.833A.833.833 0 0 0 4.166 10ZM10 4.167a.833.833 0 0 0 .833-.834V2.5a.833.833 0 1 0-1.667 0v.833a.833.833 0 0 0 .834.834ZM4.7 5.875a.833.833 0 0 0 1.175 0 .833.833 0 0 0 0-1.175l-.592-.592a.833.833 0 0 0-1.175 1.175l.592.592Zm10 .242a.833.833 0 0 0 .583-.242l.592-.592A.832.832 0 1 0 14.7 4.108l-.534.592a.833.833 0 0 0 0 1.175.833.833 0 0 0 .55.242H14.7Zm2.8 3.05h-.834a.833.833 0 0 0 0 1.666h.834a.833.833 0 1 0 0-1.666ZM10 15.833a.833.833 0 0 0-.834.834v.833a.833.833 0 1 0 1.667 0v-.833a.833.833 0 0 0-.833-.834Zm5.3-1.666a.833.833 0 0 0-1.134 1.133l.592.592a.833.833 0 0 0 1.175 0 .833.833 0 0 0 0-1.175l-.633-.55ZM10 5.417A4.583 4.583 0 1 0 14.583 10 4.592 4.592 0 0 0 10 5.417Zm0 7.5a2.917 2.917 0 1 1 0-5.833 2.917 2.917 0 0 1 0 5.833Z" fill="currentColor"/> <path d="m4.7 14.167-.592.591a.833.833 0 0 0 0 1.175.833.833 0 0 0 1.175 0l.592-.591A.833.833 0 0 0 4.7 14.167ZM4.167 10a.833.833 0 0 0-.834-.833H2.5a.833.833 0 0 0 0 1.666h.833A.833.833 0 0 0 4.167 10ZM10 4.167a.833.833 0 0 0 .833-.834V2.5a.833.833 0 1 0-1.666 0v.833a.833.833 0 0 0 .833.834ZM4.7 5.875a.833.833 0 0 0 1.175 0 .833.833 0 0 0 0-1.175l-.592-.592a.833.833 0 0 0-1.175 1.175l.592.592Zm10 .242a.833.833 0 0 0 .583-.242l.592-.592a.835.835 0 0 0-.574-1.465.833.833 0 0 0-.601.29l-.533.592a.833.833 0 0 0 0 1.175.833.833 0 0 0 .55.242H14.7Zm2.8 3.05h-.833a.833.833 0 0 0 0 1.666h.833a.833.833 0 0 0 0-1.666ZM10 15.833a.834.834 0 0 0-.833.834v.833a.833.833 0 0 0 1.666 0v-.833a.833.833 0 0 0-.833-.834Zm5.3-1.666a.834.834 0 0 0-1.133 1.133l.591.592a.833.833 0 0 0 1.175 0 .833.833 0 0 0 0-1.175l-.633-.55ZM10 5.417A4.583 4.583 0 1 0 14.583 10 4.592 4.592 0 0 0 10 5.417Zm0 7.5a2.917 2.917 0 1 1 0-5.834 2.917 2.917 0 0 1 0 5.834Z" fill="currentColor"/>
</svg> </svg>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.392 5.45c.252-.288.592-.45.948-.45h8.038a.63.63 0 0 1 .474.225l4.689 5.385a.83.83 0 0 1 .196.544v8.574l-.985 1.055-.355-.38v-8.666h-4.485a.702.702 0 0 1-.702-.702V6.538H7.34v16.924h9.44L18.217 25H7.34c-.356 0-.696-.162-.948-.45A1.661 1.661 0 0 1 6 23.461V6.538c0-.408.141-.799.392-1.087Zm9.222 1.678 2.791 3.205h-2.791V7.128ZM8.85 15.5a.65.65 0 0 1 .65-.65h7.2a.65.65 0 1 1 0 1.3H9.5a.65.65 0 0 1-.65-.65Zm0 2.4a.65.65 0 0 1 .65-.65h7.2a.65.65 0 1 1 0 1.3H9.5a.65.65 0 0 1-.65-.65Z" fill="currentColor"/>
<path d="m17.552 21.357 2.2 2.357 4.4-4.714" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import { TextEncoder, TextDecoder } from 'util';
import fetchMock from 'jest-fetch-mock'; import fetchMock from 'jest-fetch-mock';
...@@ -6,6 +7,8 @@ fetchMock.enableMocks(); ...@@ -6,6 +7,8 @@ fetchMock.enableMocks();
const envs = dotenv.config({ path: './configs/envs/.env.jest' }); const envs = dotenv.config({ path: './configs/envs/.env.jest' });
Object.assign(global, { TextDecoder, TextEncoder });
Object.defineProperty(window, 'matchMedia', { Object.defineProperty(window, 'matchMedia', {
writable: true, writable: true,
value: jest.fn().mockImplementation(query => ({ value: jest.fn().mockImplementation(query => ({
......
...@@ -26,12 +26,15 @@ import type { ...@@ -26,12 +26,15 @@ import type {
AddressTokensFilter, AddressTokensFilter,
AddressTokensResponse, AddressTokensResponse,
AddressWithdrawalsResponse, AddressWithdrawalsResponse,
AddressNFTsResponse,
AddressCollectionsResponse,
AddressNFTTokensFilter,
} from 'types/api/address'; } from 'types/api/address';
import type { AddressesResponse } from 'types/api/addresses'; import type { AddressesResponse } from 'types/api/addresses';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse } from 'types/api/block'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse } from 'types/api/block';
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { BackendVersionConfig } from 'types/api/configs'; import type { BackendVersionConfig } from 'types/api/configs';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract'; import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig, SolidityscanReport } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts';
import type { IndexingStatus } from 'types/api/indexingStatus'; import type { IndexingStatus } from 'types/api/indexingStatus';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
...@@ -51,12 +54,21 @@ import type { ...@@ -51,12 +54,21 @@ import type {
TokenInstance, TokenInstance,
TokenInstanceTransfersCount, TokenInstanceTransfersCount,
TokenVerifiedInfo, TokenVerifiedInfo,
TokenInventoryFilters,
} from 'types/api/token'; } from 'types/api/token';
import type { TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'types/api/tokens'; import type { TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction, TransactionsResponseWatchlist } from 'types/api/transaction'; import type {
TransactionsResponseValidated,
TransactionsResponsePending,
Transaction,
TransactionsResponseWatchlist,
TransactionsSorting,
} from 'types/api/transaction';
import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges'; import type { TxStateChanges } from 'types/api/txStateChanges';
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { VisualizedContract } from 'types/api/visualization'; import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2TxnBatches'; import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2TxnBatches';
...@@ -77,16 +89,16 @@ export const SORTING_FIELDS = [ 'sort', 'order' ]; ...@@ -77,16 +89,16 @@ export const SORTING_FIELDS = [ 'sort', 'order' ];
export const RESOURCES = { export const RESOURCES = {
// ACCOUNT // ACCOUNT
csrf: { csrf: {
path: '/api/account/v1/get_csrf', path: '/api/account/v2/get_csrf',
}, },
user_info: { user_info: {
path: '/api/account/v1/user/info', path: '/api/account/v2/user/info',
}, },
email_resend: { email_resend: {
path: '/api/account/v1/email/resend', path: '/api/account/v2/email/resend',
}, },
custom_abi: { custom_abi: {
path: '/api/account/v1/user/custom_abis/:id?', path: '/api/account/v2/user/custom_abis/:id?',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
}, },
watchlist: { watchlist: {
...@@ -95,7 +107,7 @@ export const RESOURCES = { ...@@ -95,7 +107,7 @@ export const RESOURCES = {
filterFields: [ ], filterFields: [ ],
}, },
public_tags: { public_tags: {
path: '/api/account/v1/user/public_tags/:id?', path: '/api/account/v2/user/public_tags/:id?',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
}, },
private_tags_address: { private_tags_address: {
...@@ -109,7 +121,7 @@ export const RESOURCES = { ...@@ -109,7 +121,7 @@ export const RESOURCES = {
filterFields: [ ], filterFields: [ ],
}, },
api_keys: { api_keys: {
path: '/api/account/v1/user/api_keys/:id?', path: '/api/account/v2/user/api_keys/:id?',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
}, },
...@@ -235,6 +247,10 @@ export const RESOURCES = { ...@@ -235,6 +247,10 @@ export const RESOURCES = {
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
filterFields: [], filterFields: [],
}, },
tx_interpretation: {
path: '/api/v2/transactions/:hash/summary',
pathParams: [ 'hash' as const ],
},
withdrawals: { withdrawals: {
path: '/api/v2/withdrawals', path: '/api/v2/withdrawals',
filterFields: [], filterFields: [],
...@@ -305,6 +321,16 @@ export const RESOURCES = { ...@@ -305,6 +321,16 @@ export const RESOURCES = {
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
filterFields: [ 'type' as const ], filterFields: [ 'type' as const ],
}, },
address_nfts: {
path: '/api/v2/addresses/:hash/nft',
pathParams: [ 'hash' as const ],
filterFields: [ 'type' as const ],
},
address_collections: {
path: '/api/v2/addresses/:hash/nft/collections',
pathParams: [ 'hash' as const ],
filterFields: [ 'type' as const ],
},
address_withdrawals: { address_withdrawals: {
path: '/api/v2/addresses/:hash/withdrawals', path: '/api/v2/addresses/:hash/withdrawals',
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
...@@ -343,6 +369,10 @@ export const RESOURCES = { ...@@ -343,6 +369,10 @@ export const RESOURCES = {
path: '/api/v2/smart-contracts/:hash/verification/via/:method', path: '/api/v2/smart-contracts/:hash/verification/via/:method',
pathParams: [ 'hash' as const, 'method' as const ], pathParams: [ 'hash' as const, 'method' as const ],
}, },
contract_solidityscan_report: {
path: '/api/v2/smart-contracts/:hash/solidityscan-report',
pathParams: [ 'hash' as const ],
},
verified_contracts: { verified_contracts: {
path: '/api/v2/smart-contracts', path: '/api/v2/smart-contracts',
...@@ -380,7 +410,7 @@ export const RESOURCES = { ...@@ -380,7 +410,7 @@ export const RESOURCES = {
token_inventory: { token_inventory: {
path: '/api/v2/tokens/:hash/instances', path: '/api/v2/tokens/:hash/instances',
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
filterFields: [], filterFields: [ 'holder_address_hash' as const ],
}, },
tokens: { tokens: {
path: '/api/v2/tokens', path: '/api/v2/tokens',
...@@ -576,7 +606,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -576,7 +606,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'addresses' | 'addresses' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' |
'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'tokens_bridged' | 'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'tokens_bridged' |
'token_instance_transfers' | 'token_instance_holders' | 'token_instance_transfers' | 'token_instance_holders' |
'verified_contracts' | 'verified_contracts' |
...@@ -626,6 +656,7 @@ Q extends 'tx_logs' ? LogsResponseTx : ...@@ -626,6 +656,7 @@ Q extends 'tx_logs' ? LogsResponseTx :
Q extends 'tx_token_transfers' ? TokenTransferResponse : Q extends 'tx_token_transfers' ? TokenTransferResponse :
Q extends 'tx_raw_trace' ? RawTracesResponse : Q extends 'tx_raw_trace' ? RawTracesResponse :
Q extends 'tx_state_changes' ? TxStateChanges : Q extends 'tx_state_changes' ? TxStateChanges :
Q extends 'tx_interpretation' ? TxInterpretationResponse :
Q extends 'addresses' ? AddressesResponse : Q extends 'addresses' ? AddressesResponse :
Q extends 'address' ? Address : Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters : Q extends 'address_counters' ? AddressCounters :
...@@ -638,6 +669,8 @@ Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse : ...@@ -638,6 +669,8 @@ Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart : Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart :
Q extends 'address_logs' ? LogsResponseAddress : Q extends 'address_logs' ? LogsResponseAddress :
Q extends 'address_tokens' ? AddressTokensResponse : Q extends 'address_tokens' ? AddressTokensResponse :
Q extends 'address_nfts' ? AddressNFTsResponse :
Q extends 'address_collections' ? AddressCollectionsResponse :
Q extends 'address_withdrawals' ? AddressWithdrawalsResponse : Q extends 'address_withdrawals' ? AddressWithdrawalsResponse :
Q extends 'token' ? TokenInfo : Q extends 'token' ? TokenInfo :
Q extends 'token_verified_info' ? TokenVerifiedInfo : Q extends 'token_verified_info' ? TokenVerifiedInfo :
...@@ -659,6 +692,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> : ...@@ -659,6 +692,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> : Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> : Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> :
Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> : Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> :
Q extends 'contract_solidityscan_report' ? SolidityscanReport :
Q extends 'verified_contracts' ? VerifiedContractsResponse : Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters : Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
Q extends 'visualize_sol2uml' ? VisualizedContract : Q extends 'visualize_sol2uml' ? VisualizedContract :
...@@ -690,7 +724,10 @@ Q extends 'token_transfers' ? TokenTransferFilters : ...@@ -690,7 +724,10 @@ Q extends 'token_transfers' ? TokenTransferFilters :
Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters : Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters :
Q extends 'address_token_transfers' ? AddressTokenTransferFilters : Q extends 'address_token_transfers' ? AddressTokenTransferFilters :
Q extends 'address_tokens' ? AddressTokensFilter : Q extends 'address_tokens' ? AddressTokensFilter :
Q extends 'address_nfts' ? AddressNFTTokensFilter :
Q extends 'address_collections' ? AddressNFTTokensFilter :
Q extends 'search' ? SearchResultFilters : Q extends 'search' ? SearchResultFilters :
Q extends 'token_inventory' ? TokenInventoryFilters :
Q extends 'tokens' ? TokensFilters : Q extends 'tokens' ? TokensFilters :
Q extends 'tokens_bridged' ? TokensBridgedFilters : Q extends 'tokens_bridged' ? TokensBridgedFilters :
Q extends 'verified_contracts' ? VerifiedContractsFilters : Q extends 'verified_contracts' ? VerifiedContractsFilters :
...@@ -701,5 +738,7 @@ never; ...@@ -701,5 +738,7 @@ never;
export type PaginationSorting<Q extends PaginatedResources> = export type PaginationSorting<Q extends PaginatedResources> =
Q extends 'tokens' ? TokensSorting : Q extends 'tokens' ? TokensSorting :
Q extends 'tokens_bridged' ? TokensSorting : Q extends 'tokens_bridged' ? TokensSorting :
Q extends 'verified_contracts' ? VerifiedContractsSorting :
Q extends 'address_txs' ? TransactionsSorting :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
...@@ -9,9 +9,11 @@ export enum NAMES { ...@@ -9,9 +9,11 @@ export enum NAMES {
CONFIRM_EMAIL_PAGE_VIEWED='confirm_email_page_viewed', CONFIRM_EMAIL_PAGE_VIEWED='confirm_email_page_viewed',
TXS_SORT='txs_sort', TXS_SORT='txs_sort',
COLOR_MODE='chakra-ui-color-mode', COLOR_MODE='chakra-ui-color-mode',
COLOR_MODE_HEX='chakra-ui-color-mode-hex',
INDEXING_ALERT='indexing_alert', INDEXING_ALERT='indexing_alert',
ADBLOCK_DETECTED='adblock_detected', ADBLOCK_DETECTED='adblock_detected',
MIXPANEL_DEBUG='_mixpanel_debug', MIXPANEL_DEBUG='_mixpanel_debug',
ADDRESS_NFT_DISPLAY_TYPE='address_nft_display_type'
} }
export function get(name?: NAMES | undefined | null, serverCookie?: string) { export function get(name?: NAMES | undefined | null, serverCookie?: string) {
......
...@@ -15,22 +15,22 @@ export default function useContractTabs(data: Address | undefined) { ...@@ -15,22 +15,22 @@ export default function useContractTabs(data: Address | undefined) {
// { id: 'contact_decompiled_code', title: 'Decompiled code', component: <div>Decompiled code</div> } : // { id: 'contact_decompiled_code', title: 'Decompiled code', component: <div>Decompiled code</div> } :
// undefined, // undefined,
data?.has_methods_read ? data?.has_methods_read ?
{ id: 'read_contract', title: 'Read contract', component: <ContractRead addressHash={ data?.hash }/> } : { id: 'read_contract', title: 'Read contract', component: <ContractRead/> } :
undefined, undefined,
data?.has_methods_read_proxy ? data?.has_methods_read_proxy ?
{ id: 'read_proxy', title: 'Read proxy', component: <ContractRead addressHash={ data?.hash } isProxy/> } : { id: 'read_proxy', title: 'Read proxy', component: <ContractRead/> } :
undefined, undefined,
data?.has_custom_methods_read ? data?.has_custom_methods_read ?
{ id: 'read_custom_methods', title: 'Read custom', component: <ContractRead addressHash={ data?.hash } isCustomAbi/> } : { id: 'read_custom_methods', title: 'Read custom', component: <ContractRead/> } :
undefined, undefined,
data?.has_methods_write ? data?.has_methods_write ?
{ id: 'write_contract', title: 'Write contract', component: <ContractWrite addressHash={ data?.hash }/> } : { id: 'write_contract', title: 'Write contract', component: <ContractWrite/> } :
undefined, undefined,
data?.has_methods_write_proxy ? data?.has_methods_write_proxy ?
{ id: 'write_proxy', title: 'Write proxy', component: <ContractWrite addressHash={ data?.hash } isProxy/> } : { id: 'write_proxy', title: 'Write proxy', component: <ContractWrite/> } :
undefined, undefined,
data?.has_custom_methods_write ? data?.has_custom_methods_write ?
{ id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite addressHash={ data?.hash } isCustomAbi/> } : { id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite/> } :
undefined, undefined,
].filter(Boolean); ].filter(Boolean);
}, [ data ]); }, [ data ]);
......
...@@ -4,27 +4,6 @@ import React from 'react'; ...@@ -4,27 +4,6 @@ import React from 'react';
import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/navigation-items'; import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/navigation-items';
import config from 'configs/app'; import config from 'configs/app';
import abiIcon from 'icons/ABI.svg';
import apiKeysIcon from 'icons/API.svg';
import appsIcon from 'icons/apps.svg';
import withdrawalsIcon from 'icons/arrows/north-east.svg';
import depositsIcon from 'icons/arrows/south-east.svg';
import blocksIcon from 'icons/block.svg';
import gearIcon from 'icons/gear.svg';
import globeIcon from 'icons/globe-b.svg';
import graphQLIcon from 'icons/graphQL.svg';
import outputRootsIcon from 'icons/output_roots.svg';
import privateTagIcon from 'icons/privattags.svg';
import publicTagIcon from 'icons/publictags.svg';
import apiDocsIcon from 'icons/restAPI.svg';
import rpcIcon from 'icons/RPC.svg';
import statsIcon from 'icons/stats.svg';
import tokensIcon from 'icons/token.svg';
import topAccountsIcon from 'icons/top-accounts.svg';
import transactionsIcon from 'icons/transactions.svg';
import txnBatchIcon from 'icons/txn_batches.svg';
import verifiedIcon from 'icons/verified.svg';
import watchlistIcon from 'icons/watchlist.svg';
import { rightLineArrow } from 'lib/html-entities'; import { rightLineArrow } from 'lib/html-entities';
import UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
...@@ -49,35 +28,43 @@ export default function useNavItems(): ReturnType { ...@@ -49,35 +28,43 @@ export default function useNavItems(): ReturnType {
return React.useMemo(() => { return React.useMemo(() => {
let blockchainNavItems: Array<NavItem> | Array<Array<NavItem>> = []; let blockchainNavItems: Array<NavItem> | Array<Array<NavItem>> = [];
const topAccounts = !config.UI.views.address.hiddenViews?.top_accounts ? { const topAccounts: NavItem | null = !config.UI.views.address.hiddenViews?.top_accounts ? {
text: 'Top accounts', text: 'Top accounts',
nextRoute: { pathname: '/accounts' as const }, nextRoute: { pathname: '/accounts' as const },
icon: topAccountsIcon, icon: 'top-accounts',
isActive: pathname === '/accounts', isActive: pathname === '/accounts',
} : null; } : null;
const blocks = { const blocks: NavItem | null = {
text: 'Blocks', text: 'Blocks',
nextRoute: { pathname: '/blocks' as const }, nextRoute: { pathname: '/blocks' as const },
icon: blocksIcon, icon: 'block',
isActive: pathname === '/blocks' || pathname === '/block/[height_or_hash]', isActive: pathname === '/blocks' || pathname === '/block/[height_or_hash]',
}; };
const txs = { const txs: NavItem | null = {
text: 'Transactions', text: 'Transactions',
nextRoute: { pathname: '/txs' as const }, nextRoute: { pathname: '/txs' as const },
icon: transactionsIcon, icon: 'transactions',
isActive: pathname === '/txs' || pathname === '/tx/[hash]', isActive: pathname === '/txs' || pathname === '/tx/[hash]',
}; };
const verifiedContracts = const verifiedContracts: NavItem | null =
// eslint-disable-next-line max-len {
{ text: 'Verified contracts', nextRoute: { pathname: '/verified-contracts' as const }, icon: verifiedIcon, isActive: pathname === '/verified-contracts' }; text: 'Verified contracts',
nextRoute: { pathname: '/verified-contracts' as const },
icon: 'verified',
isActive: pathname === '/verified-contracts',
};
if (config.features.zkEvmRollup.isEnabled) { if (config.features.zkEvmRollup.isEnabled) {
blockchainNavItems = [ blockchainNavItems = [
[ [
txs, txs,
blocks, blocks,
// eslint-disable-next-line max-len {
{ text: 'Txn batches', nextRoute: { pathname: '/zkevm-l2-txn-batches' as const }, icon: txnBatchIcon, isActive: pathname === '/zkevm-l2-txn-batches' || pathname === '/zkevm-l2-txn-batch/[number]' }, text: 'Txn batches',
nextRoute: { pathname: '/zkevm-l2-txn-batches' as const },
icon: 'txn_batches',
isActive: pathname === '/zkevm-l2-txn-batches' || pathname === '/zkevm-l2-txn-batch/[number]',
},
], ],
[ [
topAccounts, topAccounts,
...@@ -89,16 +76,16 @@ export default function useNavItems(): ReturnType { ...@@ -89,16 +76,16 @@ export default function useNavItems(): ReturnType {
[ [
txs, txs,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/l2-deposits' as const }, icon: depositsIcon, isActive: pathname === '/l2-deposits' }, { text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/l2-deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/l2-deposits' },
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/l2-withdrawals' as const }, icon: withdrawalsIcon, isActive: pathname === '/l2-withdrawals' }, { text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/l2-withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/l2-withdrawals' },
], ],
[ [
blocks, blocks,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: 'Txn batches', nextRoute: { pathname: '/l2-txn-batches' as const }, icon: txnBatchIcon, isActive: pathname === '/l2-txn-batches' }, { text: 'Txn batches', nextRoute: { pathname: '/l2-txn-batches' as const }, icon: 'txn_batches', isActive: pathname === '/l2-txn-batches' },
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: 'Output roots', nextRoute: { pathname: '/l2-output-roots' as const }, icon: outputRootsIcon, isActive: pathname === '/l2-output-roots' }, { text: 'Output roots', nextRoute: { pathname: '/l2-output-roots' as const }, icon: 'output_roots', isActive: pathname === '/l2-output-roots' },
], ],
[ [
topAccounts, topAccounts,
...@@ -114,7 +101,7 @@ export default function useNavItems(): ReturnType { ...@@ -114,7 +101,7 @@ export default function useNavItems(): ReturnType {
config.features.beaconChain.isEnabled && { config.features.beaconChain.isEnabled && {
text: 'Withdrawals', text: 'Withdrawals',
nextRoute: { pathname: '/withdrawals' as const }, nextRoute: { pathname: '/withdrawals' as const },
icon: withdrawalsIcon, icon: 'arrows/north-east',
isActive: pathname === '/withdrawals', isActive: pathname === '/withdrawals',
}, },
].filter(Boolean); ].filter(Boolean);
...@@ -124,23 +111,23 @@ export default function useNavItems(): ReturnType { ...@@ -124,23 +111,23 @@ export default function useNavItems(): ReturnType {
config.features.restApiDocs.isEnabled ? { config.features.restApiDocs.isEnabled ? {
text: 'REST API', text: 'REST API',
nextRoute: { pathname: '/api-docs' as const }, nextRoute: { pathname: '/api-docs' as const },
icon: apiDocsIcon, icon: 'restAPI',
isActive: pathname === '/api-docs', isActive: pathname === '/api-docs',
} : null, } : null,
config.features.graphqlApiDocs.isEnabled ? { config.features.graphqlApiDocs.isEnabled ? {
text: 'GraphQL', text: 'GraphQL',
nextRoute: { pathname: '/graphiql' as const }, nextRoute: { pathname: '/graphiql' as const },
icon: graphQLIcon, icon: 'graphQL',
isActive: pathname === '/graphiql', isActive: pathname === '/graphiql',
} : null, } : null,
!config.UI.sidebar.hiddenLinks?.rpc_api && { !config.UI.sidebar.hiddenLinks?.rpc_api && {
text: 'RPC API', text: 'RPC API',
icon: rpcIcon, icon: 'RPC',
url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints', url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints',
}, },
!config.UI.sidebar.hiddenLinks?.eth_rpc_api && { !config.UI.sidebar.hiddenLinks?.eth_rpc_api && {
text: 'Eth RPC API', text: 'Eth RPC API',
icon: rpcIcon, icon: 'RPC',
url: ' https://docs.blockscout.com/for-users/api/eth-rpc', url: ' https://docs.blockscout.com/for-users/api/eth-rpc',
}, },
].filter(Boolean); ].filter(Boolean);
...@@ -148,74 +135,83 @@ export default function useNavItems(): ReturnType { ...@@ -148,74 +135,83 @@ export default function useNavItems(): ReturnType {
const mainNavItems: ReturnType['mainNavItems'] = [ const mainNavItems: ReturnType['mainNavItems'] = [
{ {
text: 'Blockchain', text: 'Blockchain',
icon: globeIcon, icon: 'globe-b',
isActive: blockchainNavItems.flat().some(item => isInternalItem(item) && item.isActive), isActive: blockchainNavItems.flat().some(item => isInternalItem(item) && item.isActive),
subItems: blockchainNavItems, subItems: blockchainNavItems,
}, },
{ {
text: 'Tokens', text: 'Tokens',
nextRoute: { pathname: '/tokens' as const }, nextRoute: { pathname: '/tokens' as const },
icon: tokensIcon, icon: 'token',
isActive: pathname.startsWith('/token'), isActive: pathname.startsWith('/token'),
}, },
config.features.marketplace.isEnabled ? { config.features.marketplace.isEnabled ? {
text: 'Apps', text: 'Apps',
nextRoute: { pathname: '/apps' as const }, nextRoute: { pathname: '/apps' as const },
icon: appsIcon, icon: 'apps',
isActive: pathname.startsWith('/app'), isActive: pathname.startsWith('/app'),
} : null, } : null,
config.features.stats.isEnabled ? { config.features.stats.isEnabled ? {
text: 'Charts & stats', text: 'Charts & stats',
nextRoute: { pathname: '/stats' as const }, nextRoute: { pathname: '/stats' as const },
icon: statsIcon, icon: 'stats',
isActive: pathname === '/stats', isActive: pathname === '/stats',
} : null, } : null,
apiNavItems.length > 0 && { apiNavItems.length > 0 && {
text: 'API', text: 'API',
icon: apiDocsIcon, icon: 'restAPI',
isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive), isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive),
subItems: apiNavItems, subItems: apiNavItems,
}, },
config.UI.sidebar.otherLinks.length > 0 ? { {
text: 'Other', text: 'Other',
icon: gearIcon, icon: 'gear',
subItems: config.UI.sidebar.otherLinks, subItems: [
} : null, {
text: 'Verify contract',
nextRoute: { pathname: '/contract-verification' as const },
isActive: pathname.startsWith('/contract-verification'),
},
...config.UI.sidebar.otherLinks,
],
},
].filter(Boolean); ].filter(Boolean);
const accountNavItems: ReturnType['accountNavItems'] = [ const accountNavItems: ReturnType['accountNavItems'] = [
{ {
text: 'Watch list', text: 'Watch list',
nextRoute: { pathname: '/account/watchlist' as const }, nextRoute: { pathname: '/account/watchlist' as const },
icon: watchlistIcon, icon: 'watchlist',
isActive: pathname === '/account/watchlist', isActive: pathname === '/account/watchlist',
}, },
{ {
text: 'Private tags', text: 'Private tags',
nextRoute: { pathname: '/account/tag-address' as const }, nextRoute: { pathname: '/account/tag-address' as const },
icon: privateTagIcon, icon: 'privattags',
isActive: pathname === '/account/tag-address', isActive: pathname === '/account/tag-address',
}, },
{ {
text: 'Public tags', text: 'Public tags',
nextRoute: { pathname: '/account/public-tags-request' as const }, nextRoute: { pathname: '/account/public-tags-request' as const },
icon: publicTagIcon, isActive: pathname === '/account/public-tags-request', icon: 'publictags',
isActive: pathname === '/account/public-tags-request',
}, },
{ {
text: 'API keys', text: 'API keys',
nextRoute: { pathname: '/account/api-key' as const }, nextRoute: { pathname: '/account/api-key' as const },
icon: apiKeysIcon, isActive: pathname === '/account/api-key', icon: 'API',
isActive: pathname === '/account/api-key',
}, },
{ {
text: 'Custom ABI', text: 'Custom ABI',
nextRoute: { pathname: '/account/custom-abi' as const }, nextRoute: { pathname: '/account/custom-abi' as const },
icon: abiIcon, icon: 'ABI',
isActive: pathname === '/account/custom-abi', isActive: pathname === '/account/custom-abi',
}, },
config.features.addressVerification.isEnabled && { config.features.addressVerification.isEnabled && {
text: 'Verified addrs', text: 'Verified addrs',
nextRoute: { pathname: '/account/verified-addresses' as const }, nextRoute: { pathname: '/account/verified-addresses' as const },
icon: verifiedIcon, icon: 'verified',
isActive: pathname === '/account/verified-addresses', isActive: pathname === '/account/verified-addresses',
}, },
].filter(Boolean); ].filter(Boolean);
......
// https://unicode-table.com // https://symbl.cc/en/
export const asymp = String.fromCharCode(8776); // ~ export const asymp = String.fromCharCode(8776); // ≈
export const tilde = String.fromCharCode(126); // ~
export const hellip = String.fromCharCode(8230); // … export const hellip = String.fromCharCode(8230); // …
export const nbsp = String.fromCharCode(160); // no-break Space export const nbsp = String.fromCharCode(160); // no-break Space
export const thinsp = String.fromCharCode(8201); // thin Space export const thinsp = String.fromCharCode(8201); // thin Space
......
...@@ -12,6 +12,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -12,6 +12,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/accounts': 'Root page', '/accounts': 'Root page',
'/address/[hash]': 'Regular page', '/address/[hash]': 'Regular page',
'/verified-contracts': 'Root page', '/verified-contracts': 'Root page',
'/contract-verification': 'Root page',
'/address/[hash]/contract-verification': 'Regular page', '/address/[hash]/contract-verification': 'Regular page',
'/tokens': 'Root page', '/tokens': 'Root page',
'/token/[hash]': 'Regular page', '/token/[hash]': 'Regular page',
......
...@@ -15,6 +15,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -15,6 +15,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/accounts': DEFAULT_TEMPLATE, '/accounts': DEFAULT_TEMPLATE,
'/address/[hash]': 'View the account balance, transactions, and other data for %hash% on the %network_title%', '/address/[hash]': 'View the account balance, transactions, and other data for %hash% on the %network_title%',
'/verified-contracts': DEFAULT_TEMPLATE, '/verified-contracts': DEFAULT_TEMPLATE,
'/contract-verification': DEFAULT_TEMPLATE,
'/address/[hash]/contract-verification': 'View the account balance, transactions, and other data for %hash% on the %network_title%', '/address/[hash]/contract-verification': 'View the account balance, transactions, and other data for %hash% on the %network_title%',
'/tokens': DEFAULT_TEMPLATE, '/tokens': DEFAULT_TEMPLATE,
'/token/[hash]': '%hash%, balances and analytics on the %network_title%', '/token/[hash]': '%hash%, balances and analytics on the %network_title%',
......
...@@ -10,6 +10,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -10,6 +10,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/accounts': 'top accounts', '/accounts': 'top accounts',
'/address/[hash]': 'address details for %hash%', '/address/[hash]': 'address details for %hash%',
'/verified-contracts': 'verified contracts', '/verified-contracts': 'verified contracts',
'/contract-verification': 'verify contract',
'/address/[hash]/contract-verification': 'contract verification for %hash%', '/address/[hash]/contract-verification': 'contract verification for %hash%',
'/tokens': 'tokens', '/tokens': 'tokens',
'/token/[hash]': '%symbol% token details', '/token/[hash]': '%symbol% token details',
......
...@@ -10,7 +10,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -10,7 +10,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/accounts': 'Top accounts', '/accounts': 'Top accounts',
'/address/[hash]': 'Address details', '/address/[hash]': 'Address details',
'/verified-contracts': 'Verified contracts', '/verified-contracts': 'Verified contracts',
'/address/[hash]/contract-verification': 'Contract verification', '/contract-verification': 'Contract verification',
'/address/[hash]/contract-verification': 'Contract verification for address',
'/tokens': 'Tokens', '/tokens': 'Tokens',
'/token/[hash]': 'Token details', '/token/[hash]': 'Token details',
'/token/[hash]/instance/[id]': 'Token Instance', '/token/[hash]/instance/[id]': 'Token Instance',
......
...@@ -13,6 +13,7 @@ export enum EventTypes { ...@@ -13,6 +13,7 @@ export enum EventTypes {
CONTRACT_VERIFICATION = 'Contract verification', CONTRACT_VERIFICATION = 'Contract verification',
QR_CODE = 'QR code', QR_CODE = 'QR code',
PAGE_WIDGET = 'Page widget', PAGE_WIDGET = 'Page widget',
TX_INTERPRETATION_INTERACTION = 'Transaction interpratetion interaction'
} }
/* eslint-disable @typescript-eslint/indent */ /* eslint-disable @typescript-eslint/indent */
...@@ -61,6 +62,7 @@ Type extends EventTypes.VERIFY_TOKEN ? { ...@@ -61,6 +62,7 @@ Type extends EventTypes.VERIFY_TOKEN ? {
'Action': 'Form opened' | 'Submit'; 'Action': 'Form opened' | 'Submit';
} : } :
Type extends EventTypes.WALLET_CONNECT ? { Type extends EventTypes.WALLET_CONNECT ? {
'Source': 'Header' | 'Smart contracts';
'Status': 'Started' | 'Connected'; 'Status': 'Started' | 'Connected';
} : } :
Type extends EventTypes.CONTRACT_INTERACTION ? { Type extends EventTypes.CONTRACT_INTERACTION ? {
...@@ -77,5 +79,8 @@ Type extends EventTypes.QR_CODE ? { ...@@ -77,5 +79,8 @@ Type extends EventTypes.QR_CODE ? {
Type extends EventTypes.PAGE_WIDGET ? { Type extends EventTypes.PAGE_WIDGET ? {
'Type': 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)'; 'Type': 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)';
} : } :
Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? {
'Type': 'Address click' | 'Token click';
} :
undefined; undefined;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
import type * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import appConfig from 'configs/app'; import appConfig from 'configs/app';
...@@ -10,12 +11,13 @@ export const config: Sentry.BrowserOptions | undefined = (() => { ...@@ -10,12 +11,13 @@ export const config: Sentry.BrowserOptions | undefined = (() => {
} }
const tracesSampleRate: number | undefined = (() => { const tracesSampleRate: number | undefined = (() => {
if (feature.environment === 'staging' || feature.environment === 'development') { switch (feature.environment) {
return 1; case 'development':
} return 1;
case 'staging':
if (feature.environment === 'production') { return 0.75;
return 0.2; case 'production':
return 0.2;
} }
})(); })();
...@@ -25,6 +27,7 @@ export const config: Sentry.BrowserOptions | undefined = (() => { ...@@ -25,6 +27,7 @@ export const config: Sentry.BrowserOptions | undefined = (() => {
release: feature.release, release: feature.release,
enableTracing: feature.enableTracing, enableTracing: feature.enableTracing,
tracesSampleRate, tracesSampleRate,
integrations: feature.enableTracing ? [ new BrowserTracing() ] : undefined,
// error filtering settings // error filtering settings
// were taken from here - https://docs.sentry.io/platforms/node/guides/azure-functions/configuration/filtering/#decluttering-sentry // were taken from here - https://docs.sentry.io/platforms/node/guides/azure-functions/configuration/filtering/#decluttering-sentry
...@@ -53,6 +56,7 @@ export const config: Sentry.BrowserOptions | undefined = (() => { ...@@ -53,6 +56,7 @@ export const config: Sentry.BrowserOptions | undefined = (() => {
'Script error.', 'Script error.',
// Relay and WalletConnect errors // Relay and WalletConnect errors
'The quota has been exceeded',
'Attempt to connect to relay via', 'Attempt to connect to relay via',
'WebSocket connection failed for URL: wss://relay.walletconnect.com', 'WebSocket connection failed for URL: wss://relay.walletconnect.com',
], ],
...@@ -64,9 +68,11 @@ export const config: Sentry.BrowserOptions | undefined = (() => { ...@@ -64,9 +68,11 @@ export const config: Sentry.BrowserOptions | undefined = (() => {
// Woopra flakiness // Woopra flakiness
/eatdifferent\.com\.woopra-ns\.com/i, /eatdifferent\.com\.woopra-ns\.com/i,
/static\.woopra\.com\/js\/woopra\.js/i, /static\.woopra\.com\/js\/woopra\.js/i,
// Chrome extensions // Chrome and other extensions
/extensions\//i, /extensions\//i,
/^chrome:\/\//i, /^chrome:\/\//i,
/^chrome-extension:\/\//i,
/^moz-extension:\/\//i,
// Other plugins // Other plugins
/127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
/webappstoolbarba\.texthelp\.com\//i, /webappstoolbarba\.texthelp\.com\//i,
...@@ -87,3 +93,12 @@ export function configureScope(scope: Sentry.Scope) { ...@@ -87,3 +93,12 @@ export function configureScope(scope: Sentry.Scope) {
} }
scope.setTag('app_instance', feature.instance); scope.setTag('app_instance', feature.instance);
} }
export function init() {
if (!config) {
return;
}
Sentry.init(config);
Sentry.configureScope(configureScope);
}
import * as Sentry from '@sentry/react';
import React from 'react';
import { config, configureScope } from './config';
export default function useConfigSentry() {
React.useEffect(() => {
if (!config) {
return;
}
// gotta init sentry in browser
Sentry.init(config);
Sentry.configureScope(configureScope);
}, []);
}
import type { TokenType } from 'types/api/token'; import type { NFTTokenType, TokenType } from 'types/api/token';
export const TOKEN_TYPES: Array<{ title: string; id: TokenType }> = [ export const NFT_TOKEN_TYPES: Array<{ title: string; id: NFTTokenType }> = [
{ title: 'ERC-20', id: 'ERC-20' },
{ title: 'ERC-721', id: 'ERC-721' }, { title: 'ERC-721', id: 'ERC-721' },
{ title: 'ERC-1155', id: 'ERC-1155' }, { title: 'ERC-1155', id: 'ERC-1155' },
]; ];
export const TOKEN_TYPES: Array<{ title: string; id: TokenType }> = [
{ title: 'ERC-20', id: 'ERC-20' },
...NFT_TOKEN_TYPES,
];
export const NFT_TOKEN_TYPE_IDS = NFT_TOKEN_TYPES.map(i => i.id);
export const TOKEN_TYPE_IDS = TOKEN_TYPES.map(i => i.id); export const TOKEN_TYPE_IDS = TOKEN_TYPES.map(i => i.id);
import type { Transaction } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort';
import compareBns from 'lib/bigint/compareBns';
const sortTxs = (sorting?: Sort) => (tx1: Transaction, tx2: Transaction) => {
switch (sorting) {
case 'val-desc':
return compareBns(tx1.value, tx2.value);
case 'val-asc':
return compareBns(tx2.value, tx1.value);
case 'fee-desc':
return compareBns(tx1.fee.value, tx2.fee.value);
case 'fee-asc':
return compareBns(tx2.fee.value, tx1.fee.value);
default:
return 0;
}
};
export default sortTxs;
import type { WalletType, WalletInfo } from 'types/client/wallets'; import type { WalletType, WalletInfo } from 'types/client/wallets';
import coinbaseIcon from 'icons/wallets/coinbase.svg';
import metamaskIcon from 'icons/wallets/metamask.svg';
import tokenPocketIcon from 'icons/wallets/token-pocket.svg';
export const WALLETS_INFO: Record<Exclude<WalletType, 'none'>, WalletInfo> = { export const WALLETS_INFO: Record<Exclude<WalletType, 'none'>, WalletInfo> = {
metamask: { metamask: {
name: 'MetaMask', name: 'MetaMask',
icon: metamaskIcon, icon: 'wallets/metamask',
}, },
coinbase: { coinbase: {
name: 'Coinbase Wallet', name: 'Coinbase Wallet',
icon: coinbaseIcon, icon: 'wallets/coinbase',
}, },
token_pocket: { token_pocket: {
name: 'TokenPocket', name: 'TokenPocket',
icon: tokenPocketIcon, icon: 'wallets/token-pocket',
}, },
}; };
import type { AddressTokenBalance } from 'types/api/address'; import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance } from 'types/api/address';
import * as tokens from 'mocks/tokens/tokenInfo'; import * as tokens from 'mocks/tokens/tokenInfo';
import * as tokenInstance from 'mocks/tokens/tokenInstance'; import * as tokenInstance from 'mocks/tokens/tokenInstance';
...@@ -117,3 +117,51 @@ export const erc1155List = { ...@@ -117,3 +117,51 @@ export const erc1155List = {
erc1155b, erc1155b,
], ],
}; };
export const nfts: AddressNFTsResponse = {
items: [
{
...tokenInstance.base,
token: tokens.tokenInfoERC1155a,
token_type: 'ERC-1155',
value: '11',
},
{
...tokenInstance.unique,
token: tokens.tokenInfoERC721a,
token_type: 'ERC-721',
value: '1',
},
],
next_page_params: null,
};
const nftInstance = {
...tokenInstance.base,
token_type: 'ERC-1155',
value: '11',
};
export const collections: AddressCollectionsResponse = {
items: [
{
token: tokens.tokenInfoERC1155a,
amount: '100',
token_instances: Array(5).fill(nftInstance),
},
{
token: tokens.tokenInfoERC20LongSymbol,
amount: '100',
token_instances: Array(5).fill(nftInstance),
},
{
token: tokens.tokenInfoERC1155WithoutName,
amount: '1',
token_instances: [ nftInstance ],
},
],
next_page_params: {
token_contract_address_hash: '123',
token_type: 'ERC-1155',
},
};
...@@ -9,12 +9,12 @@ export const read: Array<SmartContractReadMethod> = [ ...@@ -9,12 +9,12 @@ export const read: Array<SmartContractReadMethod> = [
{ {
constant: true, constant: true,
inputs: [ inputs: [
{ internalType: 'address', name: '', type: 'address' }, { internalType: 'address', name: 'wallet', type: 'address' },
], ],
method_id: '70a08231', method_id: '70a08231',
name: 'FLASHLOAN_PREMIUM_TOTAL', name: 'FLASHLOAN_PREMIUM_TOTAL',
outputs: [ outputs: [
{ internalType: 'uint256', name: '', type: 'uint256' }, { internalType: 'uint256', name: 'amount', type: 'uint256' },
], ],
payable: false, payable: false,
stateMutability: 'view', stateMutability: 'view',
...@@ -97,7 +97,7 @@ export const read: Array<SmartContractReadMethod> = [ ...@@ -97,7 +97,7 @@ export const read: Array<SmartContractReadMethod> = [
export const readResultSuccess: SmartContractQueryMethodReadSuccess = { export const readResultSuccess: SmartContractQueryMethodReadSuccess = {
is_error: false, is_error: false,
result: { result: {
names: [ 'uint256' ], names: [ 'amount' ],
output: [ output: [
{ type: 'uint256', value: '42' }, { type: 'uint256', value: '42' },
], ],
......
export const solidityscanReportAverage = {
scan_report: {
scan_status: 'scan_done',
scan_summary: {
issue_severity_distribution: {
critical: 0,
gas: 1,
high: 0,
informational: 0,
low: 2,
medium: 0,
},
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '72.22',
threat_score: '94.74',
},
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
},
};
export const solidityscanReportGreat = {
scan_report: {
scan_status: 'scan_done',
scan_summary: {
issue_severity_distribution: {
critical: 0,
gas: 0,
high: 0,
informational: 0,
low: 0,
medium: 0,
},
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '100',
threat_score: '94.74',
},
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
},
};
export const solidityscanReportLow = {
scan_report: {
scan_status: 'scan_done',
scan_summary: {
issue_severity_distribution: {
critical: 2,
gas: 1,
high: 3,
informational: 0,
low: 2,
medium: 10,
},
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '22.22',
threat_score: '94.74',
},
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
},
};
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import type { TokenInstance } from 'types/api/token'; import type { TokenInstance } from 'types/api/token';
import * as addressMock from '../address/address'; import * as addressMock from '../address/address';
import { tokenInfoERC721a } from './tokenInfo';
export const base: TokenInstance = { export const base: TokenInstance = {
animation_url: null, animation_url: null,
...@@ -74,7 +73,6 @@ export const base: TokenInstance = { ...@@ -74,7 +73,6 @@ export const base: TokenInstance = {
name: 'GENESIS #188848, 22a5f8bbb1602995. Blockchain pixel PFP NFT + "on music video" trait inspired by God', name: 'GENESIS #188848, 22a5f8bbb1602995. Blockchain pixel PFP NFT + "on music video" trait inspired by God',
}, },
owner: addressMock.withName, owner: addressMock.withName,
token: tokenInfoERC721a,
}; };
export const withRichMetadata: TokenInstance = { export const withRichMetadata: TokenInstance = {
......
import type { TxInterpretationResponse } from 'types/api/txInterpretation';
export const txInterpretation: TxInterpretationResponse = {
data: {
summaries: [ {
summary_template: `{action_type} {amount} {token} to {to_address} on {timestamp}`,
summary_template_variables: {
action_type: { type: 'string', value: 'Transfer' },
amount: { type: 'currency', value: '100' },
token: {
type: 'token',
value: {
name: 'Duck',
type: 'ERC-20',
symbol: 'DUCK',
address: '0x486a3c5f34cDc4EF133f248f1C81168D78da52e8',
holders: '1152',
decimals: '18',
icon_url: null,
total_supply: '210000000000000000000000000',
exchange_rate: null,
circulating_market_cap: null,
},
},
to_address: {
type: 'address',
value: {
hash: '0x48c04ed5691981C42154C6167398f95e8f38a7fF',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
},
timestamp: {
type: 'timestamp',
value: '1687005431',
},
},
} ],
},
};
...@@ -31,6 +31,7 @@ const moduleExports = { ...@@ -31,6 +31,7 @@ const moduleExports = {
}, },
); );
config.resolve.fallback = { fs: false, net: false, tls: false }; config.resolve.fallback = { fs: false, net: false, tls: false };
config.externals.push('pino-pretty', 'lokijs', 'encoding');
return config; return config;
}, },
......
...@@ -7,18 +7,19 @@ import useAdblockDetect from 'lib/hooks/useAdblockDetect'; ...@@ -7,18 +7,19 @@ import useAdblockDetect from 'lib/hooks/useAdblockDetect';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import * as metadata from 'lib/metadata'; import * as metadata from 'lib/metadata';
import * as mixpanel from 'lib/mixpanel'; import * as mixpanel from 'lib/mixpanel';
import useConfigSentry from 'lib/sentry/useConfigSentry'; import { init as initSentry } from 'lib/sentry/config';
type Props = Route & { type Props = Route & {
children: React.ReactNode; children: React.ReactNode;
} }
initSentry();
const PageNextJs = (props: Props) => { const PageNextJs = (props: Props) => {
const { title, description, opengraph } = metadata.generate(props); const { title, description, opengraph } = metadata.generate(props);
useGetCsrfToken(); useGetCsrfToken();
useAdblockDetect(); useAdblockDetect();
useConfigSentry();
const isMixpanelInited = mixpanel.useInit(); const isMixpanelInited = mixpanel.useInit();
mixpanel.useLogPageView(isMixpanelInited); mixpanel.useLogPageView(isMixpanelInited);
......
...@@ -9,11 +9,11 @@ export function ad(): CspDev.DirectiveDescriptor { ...@@ -9,11 +9,11 @@ export function ad(): CspDev.DirectiveDescriptor {
'connect-src': [ 'connect-src': [
'coinzilla.com', 'coinzilla.com',
'*.coinzilla.com', '*.coinzilla.com',
'request-global.czilladx.com', 'https://request-global.czilladx.com',
'*.slise.xyz', '*.slise.xyz',
], ],
'frame-src': [ 'frame-src': [
'request-global.czilladx.com', 'https://request-global.czilladx.com',
], ],
'script-src': [ 'script-src': [
'coinzillatag.com', 'coinzillatag.com',
...@@ -27,7 +27,7 @@ export function ad(): CspDev.DirectiveDescriptor { ...@@ -27,7 +27,7 @@ export function ad(): CspDev.DirectiveDescriptor {
'cdn.coinzilla.io', 'cdn.coinzilla.io',
], ],
'font-src': [ 'font-src': [
'request-global.czilladx.com', 'https://request-global.czilladx.com',
], ],
}; };
} }
...@@ -9,7 +9,6 @@ import { KEY_WORDS } from '../utils'; ...@@ -9,7 +9,6 @@ import { KEY_WORDS } from '../utils';
const MAIN_DOMAINS = [ const MAIN_DOMAINS = [
`*.${ config.app.host }`, `*.${ config.app.host }`,
config.app.host, config.app.host,
getFeaturePayload(config.features.sol2uml)?.api.endpoint,
].filter(Boolean); ].filter(Boolean);
const getCspReportUrl = () => { const getCspReportUrl = () => {
...@@ -113,6 +112,7 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -113,6 +112,7 @@ export function app(): CspDev.DirectiveDescriptor {
'font-src': [ 'font-src': [
KEY_WORDS.DATA, KEY_WORDS.DATA,
...MAIN_DOMAINS,
], ],
'object-src': [ 'object-src': [
......
...@@ -2,6 +2,8 @@ import type CspDev from 'csp-dev'; ...@@ -2,6 +2,8 @@ import type CspDev from 'csp-dev';
import config from 'configs/app'; import config from 'configs/app';
import { KEY_WORDS } from '../utils';
export function walletConnect(): CspDev.DirectiveDescriptor { export function walletConnect(): CspDev.DirectiveDescriptor {
if (!config.features.blockchainInteraction.isEnabled) { if (!config.features.blockchainInteraction.isEnabled) {
return {}; return {};
...@@ -9,11 +11,13 @@ export function walletConnect(): CspDev.DirectiveDescriptor { ...@@ -9,11 +11,13 @@ export function walletConnect(): CspDev.DirectiveDescriptor {
return { return {
'connect-src': [ 'connect-src': [
'*.web3modal.com',
'*.walletconnect.com', '*.walletconnect.com',
'wss://relay.walletconnect.com', 'wss://relay.walletconnect.com',
'wss://www.walletlink.org', 'wss://www.walletlink.org',
], ],
'img-src': [ 'img-src': [
KEY_WORDS.BLOB,
'*.walletconnect.com', '*.walletconnect.com',
], ],
}; };
......
...@@ -28,6 +28,7 @@ declare module "nextjs-routes" { ...@@ -28,6 +28,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/auth/unverified-email"> | StaticRoute<"/auth/unverified-email">
| DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }> | DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }>
| StaticRoute<"/blocks"> | StaticRoute<"/blocks">
| StaticRoute<"/contract-verification">
| StaticRoute<"/csv-export"> | StaticRoute<"/csv-export">
| StaticRoute<"/graphiql"> | StaticRoute<"/graphiql">
| StaticRoute<"/"> | StaticRoute<"/">
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"npm": "8" "npm": "8"
}, },
"scripts": { "scripts": {
"dev": "next dev", "dev": "./tools/scripts/dev.sh",
"dev:preset": "./tools/scripts/dev.preset.sh", "dev:preset": "./tools/scripts/dev.preset.sh",
"build": "next build", "build": "next build",
"build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend:local ./", "build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend:local ./",
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
"lint:tsc": "tsc -p ./tsconfig.json", "lint:tsc": "tsc -p ./tsconfig.json",
"lint:envs-validator:test": "cd ./deploy/tools/envs-validator && ./test.sh", "lint:envs-validator:test": "cd ./deploy/tools/envs-validator && ./test.sh",
"prepare": "husky install", "prepare": "husky install",
"format-svg": "svgo -r ./icons", "svg:format": "svgo -r ./icons",
"svg:build-sprite": "icons build -i ./icons -o ./public/icons --optimize",
"test:pw": "./tools/scripts/pw.sh", "test:pw": "./tools/scripts/pw.sh",
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw", "test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw",
"test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.35.1-focal ./tools/scripts/pw.docker.sh", "test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.35.1-focal ./tools/scripts/pw.docker.sh",
...@@ -46,25 +47,26 @@ ...@@ -46,25 +47,26 @@
"@opentelemetry/sdk-trace-node": "^1.18.0", "@opentelemetry/sdk-trace-node": "^1.18.0",
"@opentelemetry/semantic-conventions": "^1.18.0", "@opentelemetry/semantic-conventions": "^1.18.0",
"@sentry/cli": "^2.21.2", "@sentry/cli": "^2.21.2",
"@sentry/react": "^7.72.0", "@sentry/react": "7.24.0",
"@sentry/tracing": "7.24.0",
"@slise/embed-react": "^2.2.0", "@slise/embed-react": "^2.2.0",
"@tanstack/react-query": "^5.4.3", "@tanstack/react-query": "^5.4.3",
"@tanstack/react-query-devtools": "^5.4.3", "@tanstack/react-query-devtools": "^5.4.3",
"@types/papaparse": "^5.3.5", "@types/papaparse": "^5.3.5",
"@types/react-scroll": "^1.8.4", "@types/react-scroll": "^1.8.4",
"@web3modal/ethereum": "^2.6.2", "@web3modal/wagmi": "3.5.0",
"@web3modal/react": "^2.6.2",
"bignumber.js": "^9.1.0", "bignumber.js": "^9.1.0",
"blo": "^1.1.1", "blo": "^1.1.1",
"chakra-react-select": "^4.4.3", "chakra-react-select": "^4.4.3",
"crypto-js": "^4.1.1", "crypto-js": "^4.2.0",
"d3": "^7.6.1", "d3": "^7.6.1",
"dappscout-iframe": "^0.1.0",
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"framer-motion": "^6.5.1", "framer-motion": "^6.5.1",
"gradient-avatar": "^1.0.2", "gradient-avatar": "^1.0.2",
"graphiql": "^2.2.0", "graphiql": "^2.2.0",
"graphql": "^16.6.0", "graphql": "^16.8.1",
"graphql-ws": "^5.11.3", "graphql-ws": "^5.11.3",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.0.0", "lodash": "^4.0.0",
...@@ -87,11 +89,12 @@ ...@@ -87,11 +89,12 @@
"react-identicons": "^1.2.5", "react-identicons": "^1.2.5",
"react-intersection-observer": "^9.5.2", "react-intersection-observer": "^9.5.2",
"react-jazzicon": "^1.0.4", "react-jazzicon": "^1.0.4",
"react-number-format": "^5.3.1",
"react-scroll": "^1.8.7", "react-scroll": "^1.8.7",
"swagger-ui-react": "^5.9.0", "swagger-ui-react": "^5.9.0",
"use-font-face-observer": "^1.2.1", "use-font-face-observer": "^1.2.1",
"viem": "^1.1.8", "viem": "1.20.1",
"wagmi": "^1.3.3", "wagmi": "1.4.12",
"xss": "^1.0.14" "xss": "^1.0.14"
}, },
"devDependencies": { "devDependencies": {
...@@ -135,6 +138,7 @@ ...@@ -135,6 +138,7 @@
"lint-staged": ">=10", "lint-staged": ">=10",
"mockdate": "^3.0.5", "mockdate": "^3.0.5",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"svg-icons-cli": "^0.0.5",
"svgo": "^2.8.0", "svgo": "^2.8.0",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
......
...@@ -17,6 +17,7 @@ import theme from 'theme'; ...@@ -17,6 +17,7 @@ import theme from 'theme';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import GoogleAnalytics from 'ui/shared/GoogleAnalytics'; import GoogleAnalytics from 'ui/shared/GoogleAnalytics';
import Layout from 'ui/shared/layout/Layout'; import Layout from 'ui/shared/layout/Layout';
import Web3ModalProvider from 'ui/shared/Web3ModalProvider';
import 'lib/setLocale'; import 'lib/setLocale';
...@@ -52,17 +53,19 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ...@@ -52,17 +53,19 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
{ ...ERROR_SCREEN_STYLES } { ...ERROR_SCREEN_STYLES }
onError={ handleError } onError={ handleError }
> >
<AppContextProvider pageProps={ pageProps }> <Web3ModalProvider>
<QueryClientProvider client={ queryClient }> <AppContextProvider pageProps={ pageProps }>
<ScrollDirectionProvider> <QueryClientProvider client={ queryClient }>
<SocketProvider url={ `${ config.api.socket }${ config.api.basePath }/socket/v2` }> <ScrollDirectionProvider>
{ getLayout(<Component { ...pageProps }/>) } <SocketProvider url={ `${ config.api.socket }${ config.api.basePath }/socket/v2` }>
</SocketProvider> { getLayout(<Component { ...pageProps }/>) }
</ScrollDirectionProvider> </SocketProvider>
<ReactQueryDevtools buttonPosition="bottom-left" position="left"/> </ScrollDirectionProvider>
<GoogleAnalytics/> <ReactQueryDevtools buttonPosition="bottom-left" position="left"/>
</QueryClientProvider> <GoogleAnalytics/>
</AppContextProvider> </QueryClientProvider>
</AppContextProvider>
</Web3ModalProvider>
</AppErrorBoundary> </AppErrorBoundary>
</ChakraProvider> </ChakraProvider>
); );
......
...@@ -6,6 +6,7 @@ import React from 'react'; ...@@ -6,6 +6,7 @@ import React from 'react';
import * as serverTiming from 'nextjs/utils/serverTiming'; import * as serverTiming from 'nextjs/utils/serverTiming';
import theme from 'theme'; import theme from 'theme';
import * as svgSprite from 'ui/shared/IconSvg';
class MyDocument extends Document { class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) { static async getInitialProps(ctx: DocumentContext) {
...@@ -48,6 +49,8 @@ class MyDocument extends Document { ...@@ -48,6 +49,8 @@ class MyDocument extends Document {
<link rel="icon" sizes="16x16" type="image/png"href="/favicon/favicon-16x16.png"/> <link rel="icon" sizes="16x16" type="image/png"href="/favicon/favicon-16x16.png"/>
<link rel="apple-touch-icon" href="/favicon/apple-touch-icon-180x180.png"/> <link rel="apple-touch-icon" href="/favicon/apple-touch-icon-180x180.png"/>
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg"/> <link rel="mask-icon" href="/favicon/safari-pinned-tab.svg"/>
<link rel="preload" as="image" href={ svgSprite.href }/>
</Head> </Head>
<body> <body>
<ColorModeScript initialColorMode={ theme.config.initialColorMode }/> <ColorModeScript initialColorMode={ theme.config.initialColorMode }/>
......
...@@ -4,12 +4,12 @@ import React from 'react'; ...@@ -4,12 +4,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps'; import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
import ContractVerification from 'ui/pages/ContractVerification'; import ContractVerificationForAddress from 'ui/pages/ContractVerificationForAddress';
const Page: NextPage<Props> = (props: Props) => { const Page: NextPage<Props> = (props: Props) => {
return ( return (
<PageNextJs pathname="/address/[hash]/contract-verification" query={ props }> <PageNextJs pathname="/address/[hash]/contract-verification" query={ props }>
<ContractVerification/> <ContractVerificationForAddress/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -6,6 +6,8 @@ import type { NextPageWithLayout } from 'nextjs/types'; ...@@ -6,6 +6,8 @@ import type { NextPageWithLayout } from 'nextjs/types';
import type { Props } from 'nextjs/getServerSideProps'; import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
import LayoutApp from 'ui/shared/layout/LayoutApp';
const MarketplaceApp = dynamic(() => import('ui/pages/MarketplaceApp'), { ssr: false }); const MarketplaceApp = dynamic(() => import('ui/pages/MarketplaceApp'), { ssr: false });
const Page: NextPageWithLayout<Props> = (props: Props) => { const Page: NextPageWithLayout<Props> = (props: Props) => {
...@@ -16,6 +18,14 @@ const Page: NextPageWithLayout<Props> = (props: Props) => { ...@@ -16,6 +18,14 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
); );
}; };
Page.getLayout = function getLayout(page: React.ReactElement) {
return (
<LayoutApp>
{ page }
</LayoutApp>
);
};
export default Page; export default Page;
export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps'; export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -4,15 +4,26 @@ import React from 'react'; ...@@ -4,15 +4,26 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
import config from 'configs/app';
import LinkExternal from 'ui/shared/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
const feature = config.features.marketplace;
const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false }); const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/apps"> <PageNextJs pathname="/apps">
<> <>
<PageTitle title="Marketplace"/> <PageTitle
title="DAppscout"
contentAfter={ feature.isEnabled && (
<LinkExternal href={ feature.submitFormUrl } variant="subtle" fontSize="sm" lineHeight={ 5 } ml="auto">
Submit app
</LinkExternal>
) }
/>
<Marketplace/> <Marketplace/>
</> </>
</PageNextJs> </PageNextJs>
......
import type { NextPage } from 'next';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import ContractVerification from 'ui/pages/ContractVerification';
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/contract-verification" query={ props }>
<ContractVerification/>
</PageNextJs>
);
};
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import { ChakraProvider } from '@chakra-ui/react'; import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { w3mProvider } from '@web3modal/ethereum'; import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react';
import React from 'react'; import React from 'react';
import { configureChains, createConfig, WagmiConfig } from 'wagmi'; import { WagmiConfig } from 'wagmi';
import { mainnet } from 'wagmi/chains'; import { mainnet } from 'wagmi/chains';
import type { Props as PageProps } from 'nextjs/getServerSideProps'; import type { Props as PageProps } from 'nextjs/getServerSideProps';
...@@ -33,17 +33,18 @@ const defaultAppContext = { ...@@ -33,17 +33,18 @@ const defaultAppContext = {
}; };
// >>> Web3 stuff // >>> Web3 stuff
const { publicClient } = configureChains( const chains = [ mainnet ];
[ mainnet ], const WALLET_CONNECT_PROJECT_ID = 'PROJECT_ID';
[
w3mProvider({ projectId: '' }),
],
);
const wagmiConfig = createConfig({ const wagmiConfig = defaultWagmiConfig({
autoConnect: false, chains,
connectors: [ ], projectId: WALLET_CONNECT_PROJECT_ID,
publicClient, });
createWeb3Modal({
wagmiConfig,
projectId: WALLET_CONNECT_PROJECT_ID,
chains,
}); });
// <<<< // <<<<
......
...@@ -9,5 +9,6 @@ ...@@ -9,5 +9,6 @@
<div id="root"></div> <div id="root"></div>
<script type="module" src="/playwright/envs.js"></script> <script type="module" src="/playwright/envs.js"></script>
<script type="module" src="/playwright/index.ts"></script> <script type="module" src="/playwright/index.ts"></script>
<link rel="preload" as="image" href="/public/icons/sprite.svg"/>
</body> </body>
</html> </html>
...@@ -29,6 +29,9 @@ export const featureEnvs = { ...@@ -29,6 +29,9 @@ export const featureEnvs = {
value: '[{"type":"omni","title":"OmniBridge","short_title":"OMNI"},{"type":"amb","title":"Arbitrary Message Bridge","short_title":"AMB"}]', value: '[{"type":"omni","title":"OmniBridge","short_title":"OMNI"},{"type":"amb","title":"Arbitrary Message Bridge","short_title":"AMB"}]',
}, },
], ],
txInterpretation: [
{ name: 'NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER', value: 'blockscout' },
],
zkRollup: [ zkRollup: [
{ name: 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK', value: 'true' }, { name: 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK', value: 'true' },
{ name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' }, { name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' },
......
// This file is generated by npm run build:icons
export type IconName =
| "ABI"
| "API"
| "apps"
| "arrows/down-right"
| "arrows/east-mini"
| "arrows/east"
| "arrows/north-east"
| "arrows/south-east"
| "arrows/up-down"
| "block_slim"
| "block"
| "brands/safe"
| "burger"
| "check"
| "clock-light"
| "clock"
| "coins/bitcoin"
| "collection"
| "contract_verified"
| "contract"
| "copy"
| "cross"
| "delete"
| "discussions"
| "docs"
| "donate"
| "edit"
| "email-sent"
| "email"
| "empty_search_result"
| "error-pages/404"
| "error-pages/422"
| "error-pages/429"
| "error-pages/500"
| "explorer"
| "files/csv"
| "files/image"
| "files/json"
| "files/placeholder"
| "files/sol"
| "files/yul"
| "filter"
| "finalized"
| "flame"
| "gas"
| "gear"
| "globe-b"
| "globe"
| "graphQL"
| "info"
| "key"
| "lightning"
| "link"
| "lock"
| "minus"
| "monaco/file"
| "monaco/folder-open"
| "monaco/folder"
| "monaco/solidity"
| "monaco/vyper"
| "moon-with-star"
| "moon"
| "networks"
| "networks/icon-placeholder"
| "networks/logo-placeholder"
| "nft_shield"
| "output_roots"
| "plus"
| "privattags"
| "profile"
| "publictags_slim"
| "publictags"
| "qr_code"
| "repeat_arrow"
| "restAPI"
| "rocket"
| "RPC"
| "scope"
| "score/score-not-ok"
| "score/score-ok"
| "search"
| "social/canny"
| "social/coingecko"
| "social/coinmarketcap"
| "social/defi_llama"
| "social/discord_filled"
| "social/discord"
| "social/facebook_filled"
| "social/git"
| "social/github_filled"
| "social/linkedin_filled"
| "social/medium_filled"
| "social/opensea_filled"
| "social/reddit_filled"
| "social/slack_filled"
| "social/stats"
| "social/telega"
| "social/telegram_filled"
| "social/tweet"
| "social/twitter_filled"
| "star_filled"
| "star_outline"
| "stats"
| "status/error"
| "status/pending"
| "status/success"
| "status/warning"
| "sun"
| "testnet"
| "token-placeholder"
| "token"
| "tokens"
| "tokens/xdai"
| "top-accounts"
| "transactions_slim"
| "transactions"
| "txn_batches_slim"
| "txn_batches"
| "unfinalized"
| "uniswap"
| "verified_token"
| "verified"
| "verify-contract"
| "vertical_dots"
| "wallet"
| "wallets/coinbase"
| "wallets/metamask"
| "wallets/token-pocket"
| "watchlist";
\ No newline at end of file
import type { Address, AddressCoinBalanceHistoryItem, AddressCounters, AddressTabsCounters, AddressTokenBalance } from 'types/api/address'; import type {
Address,
AddressCoinBalanceHistoryItem,
AddressCollection,
AddressCounters,
AddressNFT,
AddressTabsCounters,
AddressTokenBalance,
} from 'types/api/address';
import type { AddressesItem } from 'types/api/addresses'; import type { AddressesItem } from 'types/api/addresses';
import { ADDRESS_HASH } from './addressParams'; import { ADDRESS_HASH } from './addressParams';
...@@ -80,16 +88,22 @@ export const ADDRESS_TOKEN_BALANCE_ERC_20: AddressTokenBalance = { ...@@ -80,16 +88,22 @@ export const ADDRESS_TOKEN_BALANCE_ERC_20: AddressTokenBalance = {
value: '1000000000000000000000000', value: '1000000000000000000000000',
}; };
export const ADDRESS_TOKEN_BALANCE_ERC_721: AddressTokenBalance = { export const ADDRESS_NFT_721: AddressNFT = {
token_type: 'ERC-721',
token: TOKEN_INFO_ERC_721, token: TOKEN_INFO_ERC_721,
token_id: null, value: '1',
token_instance: null, ...TOKEN_INSTANCE,
value: '176', };
export const ADDRESS_NFT_1155: AddressNFT = {
token_type: 'ERC-1155',
token: TOKEN_INFO_ERC_1155,
value: '10',
...TOKEN_INSTANCE,
}; };
export const ADDRESS_TOKEN_BALANCE_ERC_1155: AddressTokenBalance = { export const ADDRESS_COLLECTION: AddressCollection = {
token: TOKEN_INFO_ERC_1155, token: TOKEN_INFO_ERC_1155,
token_id: '188882', amount: '4',
token_instance: TOKEN_INSTANCE, token_instances: Array(4).fill(TOKEN_INSTANCE),
value: '176',
}; };
import type { SmartContract } from 'types/api/contract'; import type { SmartContract, SolidityscanReport } from 'types/api/contract';
import type { VerifiedContract } from 'types/api/contracts'; import type { VerifiedContract } from 'types/api/contracts';
import { ADDRESS_PARAMS } from './addressParams'; import { ADDRESS_PARAMS } from './addressParams';
...@@ -53,3 +53,25 @@ export const VERIFIED_CONTRACT_INFO: VerifiedContract = { ...@@ -53,3 +53,25 @@ export const VERIFIED_CONTRACT_INFO: VerifiedContract = {
tx_count: 565058, tx_count: 565058,
verified_at: '2023-04-10T13:16:33.884921Z', verified_at: '2023-04-10T13:16:33.884921Z',
}; };
export const SOLIDITYSCAN_REPORT: SolidityscanReport = {
scan_report: {
scan_status: 'scan_done',
scan_summary: {
issue_severity_distribution: {
critical: 0,
gas: 1,
high: 0,
informational: 0,
low: 2,
medium: 0,
},
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '72.22',
threat_score: '94.74',
},
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
},
};
...@@ -108,6 +108,5 @@ export const TOKEN_INSTANCE: TokenInstance = { ...@@ -108,6 +108,5 @@ export const TOKEN_INSTANCE: TokenInstance = {
name: 'GENESIS #188882, 8a77ca1bcaa4036f. Blockchain pixel PFP NFT + "on music video" trait inspired by God', name: 'GENESIS #188882, 8a77ca1bcaa4036f. Blockchain pixel PFP NFT + "on music video" trait inspired by God',
}, },
owner: ADDRESS_PARAMS, owner: ADDRESS_PARAMS,
token: TOKEN_INFO_ERC_1155,
holder_address_hash: ADDRESS_HASH, holder_address_hash: ADDRESS_HASH,
}; };
import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import { TOKEN_INFO_ERC_20 } from './token';
export const TX_INTERPRETATION: TxInterpretationResponse = {
data: {
summaries: [
{
summary_template: '{action_type} {source_amount} Ether into {destination_amount} {destination_token}',
summary_template_variables: {
action_type: { type: 'string', value: 'Wrap' },
source_amount: { type: 'currency', value: '0.7' },
destination_amount: { type: 'currency', value: '0.7' },
destination_token: {
type: 'token',
value: TOKEN_INFO_ERC_20,
},
},
},
{
summary_template: '{action_type} {source_amount} Ether into {destination_amount} {destination_token}',
summary_template_variables: {
action_type: { type: 'string', value: 'Wrap' },
source_amount: { type: 'currency', value: '0.7' },
destination_amount: { type: 'currency', value: '0.7' },
destination_token: {
type: 'token',
value: TOKEN_INFO_ERC_20,
},
},
},
],
},
};
...@@ -5,11 +5,11 @@ module.exports = { ...@@ -5,11 +5,11 @@ module.exports = {
params: { params: {
overrides: { overrides: {
removeViewBox: false, removeViewBox: false,
removeHiddenElems: false,
}, },
}, },
}, },
'removeDimensions', 'removeDimensions',
'prefixIds',
], ],
js2svg: { js2svg: {
indent: 2, indent: 2,
......
...@@ -13,7 +13,7 @@ function getBg(props: StyleFunctionProps) { ...@@ -13,7 +13,7 @@ function getBg(props: StyleFunctionProps) {
const { theme, colorScheme: c } = props; const { theme, colorScheme: c } = props;
const darkBg = transparentize(`${ c }.200`, 0.16)(theme); const darkBg = transparentize(`${ c }.200`, 0.16)(theme);
return { return {
light: `colors.${ c }.100`, light: `colors.${ c }.${ c === 'red' ? '50' : '100' }`,
dark: darkBg, dark: darkBg,
}; };
} }
......
...@@ -14,16 +14,14 @@ if [ ! -f "$config_file" ]; then ...@@ -14,16 +14,14 @@ if [ ! -f "$config_file" ]; then
exit 1 exit 1
fi fi
if [ ! -f "$secrets_file" ]; then
echo "Error: File '$secrets_file' not found."
exit 1
fi
# download assets for the running instance # download assets for the running instance
dotenv \ dotenv \
-e $config_file \ -e $config_file \
-- bash -c './deploy/scripts/download_assets.sh ./public/assets' -- bash -c './deploy/scripts/download_assets.sh ./public/assets'
yarn svg:build-sprite
echo ""
# generate envs.js file and run the app # generate envs.js file and run the app
dotenv \ dotenv \
-v NEXT_PUBLIC_GIT_COMMIT_SHA=$(git rev-parse --short HEAD) \ -v NEXT_PUBLIC_GIT_COMMIT_SHA=$(git rev-parse --short HEAD) \
......
#!/bin/bash
# download assets for the running instance
dotenv \
-e .env.development.local \
-e .env.local \
-e .env.development \
-e .env \
-- bash -c './deploy/scripts/download_assets.sh ./public/assets'
yarn svg:build-sprite
echo ""
# generate envs.js file and run the app
dotenv \
-v NEXT_PUBLIC_GIT_COMMIT_SHA=$(git rev-parse --short HEAD) \
-v NEXT_PUBLIC_GIT_TAG=$(git describe --tags --abbrev=0) \
-e .env.secrets \
-e .env.development.local \
-e .env.local \
-e .env.development \
-e .env \
-- bash -c './deploy/scripts/make_envs_script.sh && next dev -- -p $NEXT_PUBLIC_APP_PORT' |
pino-pretty
\ No newline at end of file
...@@ -8,6 +8,8 @@ dotenv \ ...@@ -8,6 +8,8 @@ dotenv \
-e $config_file \ -e $config_file \
-- bash -c './deploy/scripts/make_envs_script.sh ./playwright/envs.js' -- bash -c './deploy/scripts/make_envs_script.sh ./playwright/envs.js'
yarn svg:build-sprite
dotenv \ dotenv \
-v NODE_OPTIONS=\"--max-old-space-size=4096\" \ -v NODE_OPTIONS=\"--max-old-space-size=4096\" \
-e $config_file \ -e $config_file \
......
...@@ -3,7 +3,7 @@ import type { Transaction } from 'types/api/transaction'; ...@@ -3,7 +3,7 @@ import type { Transaction } from 'types/api/transaction';
import type { UserTags } from './addressParams'; import type { UserTags } from './addressParams';
import type { Block } from './block'; import type { Block } from './block';
import type { InternalTransaction } from './internalTransaction'; import type { InternalTransaction } from './internalTransaction';
import type { TokenInfo, TokenInstance, TokenType } from './token'; import type { NFTTokenType, TokenInfo, TokenInstance, TokenType } from './token';
import type { TokenTransfer, TokenTransferPagination } from './tokenTransfer'; import type { TokenTransfer, TokenTransferPagination } from './tokenTransfer';
export interface Address extends UserTags { export interface Address extends UserTags {
...@@ -49,17 +49,47 @@ export interface AddressTokenBalance { ...@@ -49,17 +49,47 @@ export interface AddressTokenBalance {
token_instance: TokenInstance | null; token_instance: TokenInstance | null;
} }
export type AddressNFT = TokenInstance & {
token: TokenInfo;
token_type: Omit<TokenType, 'ERC-20'>;
value: string;
}
export type AddressCollection = {
token: TokenInfo;
amount: string;
token_instances: Array<Omit<AddressNFT, 'token'>>;
}
export interface AddressTokensResponse { export interface AddressTokensResponse {
items: Array<AddressTokenBalance>; items: Array<AddressTokenBalance>;
next_page_params: { next_page_params: {
items_count: number; items_count: number;
token_name: 'string' | null; token_name: string | null;
token_type: TokenType; token_type: TokenType;
value: number; value: number;
fiat_value: string | null; fiat_value: string | null;
} | null; } | null;
} }
export interface AddressNFTsResponse {
items: Array<AddressNFT>;
next_page_params: {
items_count: number;
token_id: string;
token_type: TokenType;
token_contract_address_hash: string;
} | null;
}
export interface AddressCollectionsResponse {
items: Array<AddressCollection>;
next_page_params: {
token_contract_address_hash: string;
token_type: TokenType;
} | null;
}
export interface AddressTokensBalancesSocketMessage { export interface AddressTokensBalancesSocketMessage {
overflow: boolean; overflow: boolean;
token_balances: Array<AddressTokenBalance>; token_balances: Array<AddressTokenBalance>;
...@@ -97,6 +127,10 @@ export type AddressTokensFilter = { ...@@ -97,6 +127,10 @@ export type AddressTokensFilter = {
type: TokenType; type: TokenType;
} }
export type AddressNFTTokensFilter = {
type: Array<NFTTokenType> | undefined;
}
export interface AddressCoinBalanceHistoryItem { export interface AddressCoinBalanceHistoryItem {
block_number: number; block_number: number;
block_timestamp: string; block_timestamp: string;
......
import type { Abi } from 'abitype'; import type { Abi, AbiType } from 'abitype';
export type SmartContractMethodArgType = 'address' | 'uint256' | 'bool' | 'string' | 'bytes' | 'bytes32' | 'bytes32[]'; export type SmartContractMethodArgType = AbiType;
export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable'; export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable';
export interface SmartContract { export interface SmartContract {
...@@ -88,6 +88,8 @@ export interface SmartContractMethodInput { ...@@ -88,6 +88,8 @@ export interface SmartContractMethodInput {
internalType?: SmartContractMethodArgType; internalType?: SmartContractMethodArgType;
name: string; name: string;
type: SmartContractMethodArgType; type: SmartContractMethodArgType;
components?: Array<SmartContractMethodInput>;
fieldType?: 'native_coin';
} }
export interface SmartContractMethodOutput extends SmartContractMethodInput { export interface SmartContractMethodOutput extends SmartContractMethodInput {
...@@ -97,10 +99,10 @@ export interface SmartContractMethodOutput extends SmartContractMethodInput { ...@@ -97,10 +99,10 @@ export interface SmartContractMethodOutput extends SmartContractMethodInput {
export interface SmartContractQueryMethodReadSuccess { export interface SmartContractQueryMethodReadSuccess {
is_error: false; is_error: false;
result: { result: {
names: Array<string>; names: Array<string | [ string, Array<string> ]>;
output: Array<{ output: Array<{
type: string; type: string;
value: string; value: string | Array<unknown>;
}>; }>;
}; };
} }
...@@ -156,3 +158,25 @@ export interface SmartContractVerificationError { ...@@ -156,3 +158,25 @@ export interface SmartContractVerificationError {
constructor_arguments?: Array<string>; constructor_arguments?: Array<string>;
name?: Array<string>; name?: Array<string>;
} }
export type SolidityscanReport = {
scan_report: {
scan_status: string;
scan_summary: {
issue_severity_distribution: {
critical: number;
gas: number;
high: number;
informational: number;
low: number;
medium: number;
};
lines_analyzed_count: number;
scan_time_taken: number;
score: string;
score_v2: string;
threat_score: string;
};
scanner_reference_url: string;
};
}
export interface Fee { export interface Fee {
type: string; type: string;
value: string; value: string | null;
} }
...@@ -3,7 +3,7 @@ export type HomeStats = { ...@@ -3,7 +3,7 @@ export type HomeStats = {
total_addresses: string; total_addresses: string;
total_transactions: string; total_transactions: string;
average_block_time: number; average_block_time: number;
coin_price: string; coin_price: string | null;
total_gas_used: string; total_gas_used: string;
transactions_today: string; transactions_today: string;
gas_used_today: string; gas_used_today: string;
...@@ -16,9 +16,9 @@ export type HomeStats = { ...@@ -16,9 +16,9 @@ export type HomeStats = {
} }
export type GasPrices = { export type GasPrices = {
average: number; average: number | null;
fast: number; fast: number | null;
slow: number; slow: number | null;
} }
export type Counters = { export type Counters = {
......
import type { TokenInfoApplication } from './account'; import type { TokenInfoApplication } from './account';
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
export type TokenType = 'ERC-20' | 'ERC-721' | 'ERC-1155'; export type NFTTokenType = 'ERC-721' | 'ERC-1155';
export type TokenType = 'ERC-20' | NFTTokenType;
export interface TokenInfo<T extends TokenType = TokenType> { export interface TokenInfo<T extends TokenType = TokenType> {
address: string; address: string;
...@@ -61,7 +62,6 @@ export interface TokenInstance { ...@@ -61,7 +62,6 @@ export interface TokenInstance {
external_app_url: string | null; external_app_url: string | null;
metadata: Record<string, unknown> | null; metadata: Record<string, unknown> | null;
owner: AddressParam | null; owner: AddressParam | null;
token: TokenInfo;
} }
export interface TokenInstanceTransfersCount { export interface TokenInstanceTransfersCount {
...@@ -78,3 +78,7 @@ export type TokenInventoryPagination = { ...@@ -78,3 +78,7 @@ export type TokenInventoryPagination = {
} }
export type TokenVerifiedInfo = Omit<TokenInfoApplication, 'id' | 'status'>; export type TokenVerifiedInfo = Omit<TokenInfoApplication, 'id' | 'status'>;
export type TokenInventoryFilters = {
holder_address_hash?: string;
}
...@@ -122,3 +122,12 @@ export type TransactionType = 'rootstock_remasc' | ...@@ -122,3 +122,12 @@ export type TransactionType = 'rootstock_remasc' |
'coin_transfer' 'coin_transfer'
export type TxsResponse = TransactionsResponseValidated | TransactionsResponsePending | BlockTransactionsResponse; export type TxsResponse = TransactionsResponseValidated | TransactionsResponsePending | BlockTransactionsResponse;
export interface TransactionsSorting {
sort: 'value' | 'fee';
order: 'asc' | 'desc';
}
export type TransactionsSortingField = TransactionsSorting['sort'];
export type TransactionsSortingValue = `${ TransactionsSortingField }-${ TransactionsSorting['order'] }`;
import type { AddressParam } from 'types/api/addressParams';
import type { TokenInfo } from 'types/api/token';
export interface TxInterpretationResponse {
data: {
summaries: Array<TxInterpretationSummary>;
};
}
export type TxInterpretationSummary = {
summary_template: string;
summary_template_variables: Record<string, TxInterpretationVariable>;
}
export type TxInterpretationVariable =
TxInterpretationVariableString |
TxInterpretationVariableCurrency |
TxInterpretationVariableTimestamp |
TxInterpretationVariableToken |
TxInterpretationVariableAddress;
export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address';
export type TxInterpretationVariableString = {
type: 'string';
value: string;
}
export type TxInterpretationVariableCurrency = {
type: 'currency';
value: string;
}
export type TxInterpretationVariableTimestamp = {
type: 'timestamp';
value: string;
}
export type TxInterpretationVariableToken = {
type: 'token';
value: TokenInfo;
}
export type TxInterpretationVariableAddress = {
type: 'address';
value: AddressParam;
}
export interface VerifiedContractsSorting {
sort: 'balance' | 'txs_count';
order: 'asc' | 'desc';
}
export type VerifiedContractsSortingField = VerifiedContractsSorting['sort'];
export type VerifiedContractsSortingValue = `${ VerifiedContractsSortingField }-${ VerifiedContractsSorting['order'] }`;
...@@ -2,8 +2,10 @@ import type React from 'react'; ...@@ -2,8 +2,10 @@ import type React from 'react';
import type { Route } from 'nextjs-routes'; import type { Route } from 'nextjs-routes';
import type { IconName } from 'ui/shared/IconSvg';
type NavIconOrComponent = { type NavIconOrComponent = {
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>; icon?: IconName;
} | { } | {
iconComponent?: React.FC<{size?: number}>; iconComponent?: React.FC<{size?: number}>;
}; };
......
import type { ArrayElement } from 'types/utils';
export const PROVIDERS = [
'blockscout',
'none',
] as const;
export type Provider = ArrayElement<typeof PROVIDERS>;
export type Sort = 'val-desc' | 'val-asc' | 'fee-desc' | 'fee-asc' | '';
import type { ArrayElement } from 'types/utils'; import type { ArrayElement } from 'types/utils';
import type { IconName } from 'ui/shared/IconSvg';
export const SUPPORTED_WALLETS = [ export const SUPPORTED_WALLETS = [
'metamask', 'metamask',
'coinbase', 'coinbase',
...@@ -10,5 +12,5 @@ export type WalletType = ArrayElement<typeof SUPPORTED_WALLETS>; ...@@ -10,5 +12,5 @@ export type WalletType = ArrayElement<typeof SUPPORTED_WALLETS>;
export interface WalletInfo { export interface WalletInfo {
name: string; name: string;
icon: React.ElementType; icon: IconName;
} }
...@@ -4,6 +4,7 @@ export const BLOCK_FIELDS_IDS = [ ...@@ -4,6 +4,7 @@ export const BLOCK_FIELDS_IDS = [
'burnt_fees', 'burnt_fees',
'total_reward', 'total_reward',
'nonce', 'nonce',
'miner',
] as const; ] as const;
export type BlockFieldId = ArrayElement<typeof BLOCK_FIELDS_IDS>; export type BlockFieldId = ArrayElement<typeof BLOCK_FIELDS_IDS>;
import { chakra, Icon, Tooltip, Hide, Skeleton, Flex } from '@chakra-ui/react'; import { chakra, Tooltip, Hide, Skeleton, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { CsvExportParams } from 'types/client/address'; import type { CsvExportParams } from 'types/client/address';
...@@ -6,9 +6,9 @@ import type { CsvExportParams } from 'types/client/address'; ...@@ -6,9 +6,9 @@ import type { CsvExportParams } from 'types/client/address';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import svgFileIcon from 'icons/files/csv.svg';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
...@@ -47,7 +47,7 @@ const AddressCsvExportLink = ({ className, address, params, isLoading }: Props) ...@@ -47,7 +47,7 @@ const AddressCsvExportLink = ({ className, address, params, isLoading }: Props)
href={ route({ pathname: '/csv-export', query: { ...params, address } }) } href={ route({ pathname: '/csv-export', query: { ...params, address } }) }
flexShrink={ 0 } flexShrink={ 0 }
> >
<Icon as={ svgFileIcon } boxSize={{ base: '30px', lg: 6 }}/> <IconSvg name="files/csv" boxSize={{ base: '30px', lg: 6 }}/>
<Hide ssr={ false } below="lg"><chakra.span ml={ 1 }>Download CSV</chakra.span></Hide> <Hide ssr={ false } below="lg"><chakra.span ml={ 1 }>Download CSV</chakra.span></Hide>
</LinkInternal> </LinkInternal>
</Tooltip> </Tooltip>
......
import { Flex, Hide, Icon, Show, Text, Tooltip, useColorModeValue } from '@chakra-ui/react'; import { Flex, Hide, Show, Text } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -9,7 +9,6 @@ import type { AddressFromToFilter, AddressTokenTransferResponse } from 'types/ap ...@@ -9,7 +9,6 @@ import type { AddressFromToFilter, AddressTokenTransferResponse } from 'types/ap
import type { TokenType } from 'types/api/token'; import type { TokenType } from 'types/api/token';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import crossIcon from 'icons/cross.svg';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery'; import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
...@@ -26,6 +25,7 @@ import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; ...@@ -26,6 +25,7 @@ import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import ResetIconButton from 'ui/shared/ResetIconButton';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter'; import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList'; import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
...@@ -115,9 +115,6 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr ...@@ -115,9 +115,6 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr
onFilterChange({}); onFilterChange({});
}, [ onFilterChange ]); }, [ onFilterChange ]);
const resetTokenIconColor = useColorModeValue('blue.600', 'blue.300');
const resetTokenIconHoverColor = useColorModeValue('blue.400', 'blue.200');
const handleNewSocketMessage: SocketMessage.AddressTokenTransfer['handler'] = (payload) => { const handleNewSocketMessage: SocketMessage.AddressTokenTransfer['handler'] = (payload) => {
setSocketAlert(''); setSocketAlert('');
...@@ -235,19 +232,7 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr ...@@ -235,19 +232,7 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr
<Flex alignItems="center" py={ 1 }> <Flex alignItems="center" py={ 1 }>
<TokenEntity.Icon token={ tokenData } isLoading={ isPlaceholderData }/> <TokenEntity.Icon token={ tokenData } isLoading={ isPlaceholderData }/>
{ isMobile ? <HashStringShorten hash={ tokenFilter }/> : tokenFilter } { isMobile ? <HashStringShorten hash={ tokenFilter }/> : tokenFilter }
<Tooltip label="Reset filter"> <ResetIconButton onClick={ resetTokenFilter }/>
<Flex>
<Icon
as={ crossIcon }
boxSize={ 5 }
ml={ 1 }
color={ resetTokenIconColor }
cursor="pointer"
_hover={{ color: resetTokenIconHoverColor }}
onClick={ resetTokenFilter }
/>
</Flex>
</Tooltip>
</Flex> </Flex>
</Flex> </Flex>
); );
......
...@@ -13,6 +13,8 @@ import AddressTokens from './AddressTokens'; ...@@ -13,6 +13,8 @@ import AddressTokens from './AddressTokens';
const ADDRESS_HASH = addressMock.withName.hash; const ADDRESS_HASH = addressMock.withName.hash;
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH }); const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }); const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH });
const API_URL_NFT = buildApiUrl('address_nfts', { hash: ADDRESS_HASH }) + '?type=';
const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH }) + '?type=';
const nextPageParams = { const nextPageParams = {
items_count: 50, items_count: 50,
...@@ -52,6 +54,14 @@ const test = base.extend({ ...@@ -52,6 +54,14 @@ const test = base.extend({
status: 200, status: 200,
body: JSON.stringify(response1155), body: JSON.stringify(response1155),
})); }));
await page.route(API_URL_NFT, (route) => route.fulfill({
status: 200,
body: JSON.stringify(tokensMock.nfts),
}));
await page.route(API_URL_COLLECTIONS, (route) => route.fulfill({
status: 200,
body: JSON.stringify(tokensMock.collections),
}));
use(page); use(page);
}, },
...@@ -76,10 +86,10 @@ test('erc20 +@dark-mode', async({ mount }) => { ...@@ -76,10 +86,10 @@ test('erc20 +@dark-mode', async({ mount }) => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('erc721 +@dark-mode', async({ mount }) => { test('collections +@dark-mode', async({ mount }) => {
const hooksConfig = { const hooksConfig = {
router: { router: {
query: { hash: ADDRESS_HASH, tab: 'tokens_erc721' }, query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true, isReady: true,
}, },
}; };
...@@ -95,10 +105,10 @@ test('erc721 +@dark-mode', async({ mount }) => { ...@@ -95,10 +105,10 @@ test('erc721 +@dark-mode', async({ mount }) => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('erc1155 +@dark-mode', async({ mount }) => { test('nfts +@dark-mode', async({ mount }) => {
const hooksConfig = { const hooksConfig = {
router: { router: {
query: { hash: ADDRESS_HASH, tab: 'tokens_erc1155' }, query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true, isReady: true,
}, },
}; };
...@@ -111,6 +121,8 @@ test('erc1155 +@dark-mode', async({ mount }) => { ...@@ -111,6 +121,8 @@ test('erc1155 +@dark-mode', async({ mount }) => {
{ hooksConfig }, { hooksConfig },
); );
await component.getByText('List').click();
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -136,10 +148,10 @@ test.describe('mobile', () => { ...@@ -136,10 +148,10 @@ test.describe('mobile', () => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('erc721', async({ mount }) => { test('nfts', async({ mount }) => {
const hooksConfig = { const hooksConfig = {
router: { router: {
query: { hash: ADDRESS_HASH, tab: 'tokens_erc721' }, query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true, isReady: true,
}, },
}; };
...@@ -152,13 +164,15 @@ test.describe('mobile', () => { ...@@ -152,13 +164,15 @@ test.describe('mobile', () => {
{ hooksConfig }, { hooksConfig },
); );
await component.getByLabel('list').click();
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('erc1155', async({ mount }) => { test('collections', async({ mount }) => {
const hooksConfig = { const hooksConfig = {
router: { router: {
query: { hash: ADDRESS_HASH, tab: 'tokens_erc1155' }, query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true, isReady: true,
}, },
}; };
......
This diff is collapsed.
...@@ -5,7 +5,7 @@ import React from 'react'; ...@@ -5,7 +5,7 @@ import React from 'react';
import type { SocketMessage } from 'lib/socket/types'; import type { SocketMessage } from 'lib/socket/types';
import type { AddressFromToFilter, AddressTransactionsResponse } from 'types/api/address'; import type { AddressFromToFilter, AddressTransactionsResponse } from 'types/api/address';
import { AddressFromToFilterValues } from 'types/api/address'; import { AddressFromToFilterValues } from 'types/api/address';
import type { Transaction } from 'types/api/transaction'; import type { Transaction, TransactionsSortingField, TransactionsSortingValue, TransactionsSorting } from 'types/api/transaction';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
...@@ -18,7 +18,10 @@ import { generateListStub } from 'stubs/utils'; ...@@ -18,7 +18,10 @@ import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import TxsContent from 'ui/txs/TxsContent'; import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting';
import { SORT_OPTIONS } from 'ui/txs/useTxsSort';
import AddressCsvExportLink from './AddressCsvExportLink'; import AddressCsvExportLink from './AddressCsvExportLink';
import AddressTxsFilter from './AddressTxsFilter'; import AddressTxsFilter from './AddressTxsFilter';
...@@ -53,6 +56,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -53,6 +56,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(''); const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0); const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const [ sort, setSort ] = React.useState<TransactionsSortingValue | undefined>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS));
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const currentAddress = getQueryParamString(router.query.hash); const currentAddress = getQueryParamString(router.query.hash);
...@@ -63,6 +67,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -63,6 +67,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
resourceName: 'address_txs', resourceName: 'address_txs',
pathParams: { hash: currentAddress }, pathParams: { hash: currentAddress },
filters: { filter: filterValue }, filters: { filter: filterValue },
sorting: getSortParamsFromValue<TransactionsSortingValue, TransactionsSortingField, TransactionsSorting['order']>(sort),
scrollRef, scrollRef,
options: { options: {
placeholderData: generateListStub<'address_txs'>(TX, 50, { next_page_params: { placeholderData: generateListStub<'address_txs'>(TX, 50, { next_page_params: {
...@@ -177,7 +182,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -177,7 +182,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
<Pagination { ...addressTxsQuery.pagination } ml={ 8 }/> <Pagination { ...addressTxsQuery.pagination } ml={ 8 }/>
</ActionBar> </ActionBar>
) } ) }
<TxsContent <TxsWithAPISorting
filter={ filter } filter={ filter }
filterValue={ filterValue } filterValue={ filterValue }
query={ addressTxsQuery } query={ addressTxsQuery }
...@@ -187,6 +192,8 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -187,6 +192,8 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
socketInfoAlert={ socketAlert } socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount } socketInfoNum={ newItemsCount }
top={ 80 } top={ 80 }
sorting={ sort }
setSort={ setSort }
/> />
</> </>
); );
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment